HIGHER EDUCATION EXPLORATORY DATA ANALYSIS AND VISUALIZATION

============

INTRODUCTION

============

============ QUESTIONS ============

The questions that I’m trying to answer with this project.

  1. What are the trends in higher-educational enrollment?
  2. Are the trends regional or discipline specific?
  3. Where should higher-education content firms invest in salesforce and content development?

============ DATA SOURCES ============

I used data from the Integrated Postsecondary Education Data System (IPEDS), which is available at https://nces.ed.gov/ipeds/use-the-data. IPEDS has data from a variety of surveys but I used the following three surveys, accessible from the drop-down menu on the website.

Fall enrollment (EFFY). This survey reports the number of enrollments in the fall semester for each responding school, college, university, etc. We use this for overall trends and also for regional trends.

Completions. This reports the number of degree completions for each institution by department (math, biology, English, etc.) and also by subdiscipline (so for mathematics the data splits into applied math, pure math, math-econ, etc.). We use this to detect trends at the level of academic discipline.

Institutional characteristics - directory information (HD). This records the region for each school. We use this to detect regional trends.

============ CONCLUSIONS ============

The answers (see below for the data analysis supporting these):

  1. Overall enrollment is down from 2013-2018. If you divide the enrollment data by gender, men’s enrollment has declined much more sharply than women’s enrollment. The data shows a slight recovery in enrollments happening around 2018, but the missing 2019 data might not suppor this, and there is the large issue of the pandemic affecting enrollment starting in 2020 

Somewhat surprisingly, there is still growth in degree completions for many disciplines, in spite of the overall downward trend in enrollment. I’m not sure how to account for this, but my guess is that in any year a large number of students enroll in college who will eventually drop out. It seems possible that downward trend in enrollment trends could eat into this group of students first: they might have less money for college, be on the fence about whether to go to college versus enter the workforce, have less preparation for college, etc. Any factor that would later contribute to a student dropping out of college might plausibly lead to them not enrolling in the first place, particularly during a time of economic uncertainty (or even during a time of economic growth, when non-degree-specific jobs are available). If this group is large enough, they could account for diminishing enrollments while other students, not as apt to drop out of college after enrolling, drive a smaller upward trend in degree completions for many academic subdisciplines.

An alternate explanation is that some universities have lowered their academic standards in response to diminishing enrollments, in a bid to lose fewer students before degree completion. With the given data I am uncertain of how to choose which of these (or both, or neither) is the correct explanation.

  1. The trends are apparently region and discipline-specific. Regionally the Far West region has seen growth and the Great Lakes region has seen decrease in enrollments, just to name two examples. At the disciplinary level, there has been dramatic decline in History degree completions accompanied by a rise in completions of technical and Computer Science degrees.

  2. Regionally, the Southeast and Far West are among the fastest growing areas. Any regional salesforce should be directed there. At the disciplinary level, Computer Science and Mathematics (including Statistics) are both growing very quickly. Within the Mathematics degree completion data we see a trend toward applied mathematics and statistics, both areas where content creation should be directed. There are several other disciplines that have seen strong growth, particularly scientific technician degrees.

============ DATA ANALYSIS ============

Here we load a few standard tools for working with dataframes and visualizations in R.

library(dplyr)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
✔ ggplot2 3.2.1     ✔ purrr   0.3.3
✔ tibble  2.1.3     ✔ stringr 1.4.0
✔ tidyr   1.0.2     ✔ forcats 0.4.0
✔ readr   1.3.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(reshape2)

Attaching package: ‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths

First, we want to load the data (stored on the disk as .csv files) into some data frames. We grab the EFFY and Completions surveys for each of the years 2010-2018. We only need one instance of the HD survey because this survey only contains regional characteristics.

df_effy_18 <- read_csv('effy2018.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_17 <- read_csv('effy2017.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_16 <- read_csv('effy2016.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_15 <- read_csv('effy2015.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_14 <- read_csv('effy2014.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_13 <- read_csv('effy2013.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_12 <- read_csv('effy2012.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_11 <- read_csv('effy2011.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYTOTLT = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEFYAIAT = col_character(),
  XEFYAIAM = col_character(),
  XEFYAIAW = col_character(),
  XEFYASIT = col_character(),
  XEFYASIM = col_character(),
  XEFYASIW = col_character(),
  XEFYBKAT = col_character(),
  XEFYBKAM = col_character(),
  XEFYBKAW = col_character(),
  XEFYHIST = col_character(),
  XEFYHISM = col_character(),
  XEFYHISW = col_character(),
  XEFYNHPT = col_character(),
  XEFYNHPM = col_character(),
  XEFYNHPW = col_character(),
  XEFYWHIT = col_character(),
  XEFYWHIM = col_character()
  # ... with 10 more columns
)
See spec(...) for full column specifications.
df_effy_10 <- read_csv('effy2010.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  XEYNRALM = col_character(),
  XEYNRALW = col_character(),
  XFYRAC03 = col_character(),
  XFYRAC04 = col_character(),
  XFYRAC05 = col_character(),
  XFYRAC06 = col_character(),
  XFYRAC07 = col_character(),
  XFYRAC08 = col_character(),
  XFYRAC09 = col_character(),
  XFYRAC10 = col_character(),
  XFYRAC11 = col_character(),
  XFYRAC12 = col_character(),
  XEYUNKNM = col_character(),
  XEYUNKNW = col_character(),
  XEYTOTLM = col_character(),
  XEYTOTLW = col_character(),
  XEYNRALT = col_character(),
  XFYRAC18 = col_character(),
  XFYRAC19 = col_character(),
  XFYRAC20 = col_character()
  # ... with 40 more columns
)
See spec(...) for full column specifications.
df_completions_18 = read_csv('c2018_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_17 = read_csv('c2017_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_16 = read_csv('c2016_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_15 = read_csv('c2015_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_14 = read_csv('c2014_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_13 = read_csv('c2013_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_12 = read_csv('c2012_a.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  CIPCODE = col_character(),
  AWLEVEL = col_character(),
  XCTOTALT = col_character(),
  XCTOTALM = col_character(),
  XCTOTALW = col_character(),
  XCAIANT = col_character(),
  XCAIANM = col_character(),
  XCAIANW = col_character(),
  XCASIAT = col_character(),
  XCASIAM = col_character(),
  XCASIAW = col_character(),
  XCBKAAT = col_character(),
  XCBKAAM = col_character(),
  XCBKAAW = col_character(),
  XCHISPT = col_character(),
  XCHISPM = col_character(),
  XCHISPW = col_character(),
  XCNHPIT = col_character(),
  XCNHPIM = col_character(),
  XCNHPIW = col_character()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_11 = read_csv('c2011_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CTOTALT = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CAIANT = col_double(),
  CAIANM = col_double(),
  CAIANW = col_double(),
  CASIAT = col_double(),
  CASIAM = col_double(),
  CASIAW = col_double(),
  CBKAAT = col_double(),
  CBKAAM = col_double(),
  CBKAAW = col_double(),
  CHISPT = col_double(),
  CHISPM = col_double(),
  CHISPW = col_double(),
  CNHPIT = col_double(),
  CNHPIM = col_double(),
  CNHPIW = col_double()
  # ... with 12 more columns
)
See spec(...) for full column specifications.
df_completions_10 = read_csv('c2010_a.csv')
Parsed with column specification:
cols(
  .default = col_character(),
  UNITID = col_double(),
  MAJORNUM = col_double(),
  CNRALM = col_double(),
  CNRALW = col_double(),
  CRACE03 = col_double(),
  CRACE04 = col_double(),
  CRACE05 = col_double(),
  CRACE06 = col_double(),
  CRACE07 = col_double(),
  CRACE08 = col_double(),
  CRACE09 = col_double(),
  CRACE10 = col_double(),
  CRACE11 = col_double(),
  CRACE12 = col_double(),
  CUNKNM = col_double(),
  CUNKNW = col_double(),
  CTOTALM = col_double(),
  CTOTALW = col_double(),
  CNRALT = col_double(),
  CRACE18 = col_double()
  # ... with 42 more columns
)
See spec(...) for full column specifications.
df_hd_14 <- read_csv('hd2014.csv')
Parsed with column specification:
cols(
  .default = col_double(),
  INSTNM = col_character(),
  ADDR = col_character(),
  CITY = col_character(),
  STABBR = col_character(),
  ZIP = col_character(),
  CHFNM = col_character(),
  CHFTITLE = col_character(),
  EIN = col_character(),
  OPEID = col_character(),
  WEBADDR = col_character(),
  ADMINURL = col_character(),
  FAIDURL = col_character(),
  APPLURL = col_character(),
  NPRICURL = col_character(),
  VETURL = col_character(),
  ATHURL = col_character(),
  ACT = col_character(),
  CLOSEDAT = col_character(),
  IALIAS = col_character(),
  F1SYSNAM = col_character()
  # ... with 1 more columns
)
See spec(...) for full column specifications.

These are fairly large tables. For example, df_effy has 16688 rows and df_hd has 7687. Lets take a peek:

head(df_effy_14)
head(df_hd_14)
head(df_completions_14)

The documentation for the data files explains the somewhat cryptic abbreviations used for column labels. For the EFFY surveys, the really important columns are UNITID (institutional ID number), EFYTOTLT (grand total fall enrollment), EFYTOTLM (total men), EFTYTOTLW (total women), LSTUDY (level of study – undergraduate 1, graduate 3, or combined 999).

Let’s get some figures from some of these tables.

enrollment_2018 <-select(df_effy_18,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2017 <-select(df_effy_17,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2016 <-select(df_effy_16,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2015 <-select(df_effy_15,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2014 <-select(df_effy_14,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2013 <-select(df_effy_13,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2012 <-select(df_effy_12,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2011 <-select(df_effy_11,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)
enrollment_2010 <-select(df_effy_10,UNITID, EFYTOTLT,EFYTOTLM,EFYTOTLW,LSTUDY)

Lets rename some of the data in these tables

enrollment_2018 <- rename(enrollment_2018,"university_id" = UNITID, "total_enrollment_2018"=EFYTOTLT,"men_enrollment_2018"=EFYTOTLM, "women_enrollment_2018"=EFYTOTLW,"level_study"=LSTUDY)

enrollment_2017 <- rename(enrollment_2017,"university_id" = UNITID, "total_enrollment_2017"=EFYTOTLT,"men_enrollment_2017"=EFYTOTLM, "women_enrollment_2017"=EFYTOTLW,"level_study"=LSTUDY)

enrollment_2016 <- rename(enrollment_2016,"university_id" = UNITID, "total_enrollment_2016"=EFYTOTLT,"men_enrollment_2016"=EFYTOTLM, "women_enrollment_2016"=EFYTOTLW,"level_study"=LSTUDY)

enrollment_2015 <- rename(enrollment_2015,"university_id" = UNITID, "total_enrollment_2015"=EFYTOTLT,"men_enrollment_2015"=EFYTOTLM, "women_enrollment_2015"=EFYTOTLW,"level_study"=LSTUDY)

enrollment_2014 <- rename(enrollment_2014,"university_id" = UNITID, "total_enrollment_2014"=EFYTOTLT,"men_enrollment_2014"=EFYTOTLM, "women_enrollment_2014"=EFYTOTLW,"level_study"=LSTUDY)
enrollment_2013 <- rename(enrollment_2013,"university_id" = UNITID,"total_enrollment_2013"=EFYTOTLT,"men_enrollment_2013"=EFYTOTLM, "women_enrollment_2013"=EFYTOTLW,"level_study"=LSTUDY)
enrollment_2012 <- rename(enrollment_2012,"university_id" = UNITID,"total_enrollment_2012"=EFYTOTLT,"men_enrollment_2012"=EFYTOTLM, "women_enrollment_2012"=EFYTOTLW,"level_study"=LSTUDY)
enrollment_2011 <- rename(enrollment_2011,"university_id" = UNITID,"total_enrollment_2011"=EFYTOTLT,"men_enrollment_2011"=EFYTOTLM, "women_enrollment_2011"=EFYTOTLW,"level_study"=LSTUDY)
enrollment_2010 <- rename(enrollment_2010,"university_id" = UNITID,"total_enrollment_2010"=EFYTOTLT,"men_enrollment_2010"=EFYTOTLM, "women_enrollment_2010"=EFYTOTLW,"level_study"=LSTUDY)

Let’s take a gander at these relabeled data sets

head(enrollment_2018)

We can see that the enrollment dataframes represent each college three times, split up by level of study. (We could further tidy the data so that it was split up by level of study AND gender, but we’ve left it this way for now.)

The next step is to combine all the enrollment data into a single data frame. We accomplish this with a pipeline of joins on the university id and level of study. The idea is that in the total_enrollment table each university is represented by three rows: one with undergraduate enrollment, one with graduate enrollment, and one with combined enrollment. Each row has data for the 9 years in question (2010-2018), which is further split up by gender.

total_enrollment <- enrollment_2010 %>%
  inner_join(enrollment_2011,by=c("university_id","level_study")) %>%
  inner_join(enrollment_2012, by =c("university_id","level_study"))%>%
  inner_join(enrollment_2013,by=c("university_id","level_study"))%>%
  inner_join(enrollment_2014,by=c("university_id","level_study"))%>%
  inner_join(enrollment_2015,by=c("university_id","level_study")) %>%
  inner_join(enrollment_2016, by =c("university_id","level_study"))%>%
  inner_join(enrollment_2017,by=c("university_id","level_study"))%>%
  inner_join(enrollment_2018,by=c("university_id","level_study"))
head(total_enrollment)

This table is quite nice – it contains the data for each university. Let’s split it up into combined/undergrad/grad tables. I mainly did this just as an exercise in pipeline and filtering.


total_enrollment_combined <- total_enrollment %>%
  filter(level_study==999) %>%
  select(-level_study)

total_enrollment_undergrad <- total_enrollment %>%
  filter(level_study==1)%>%
  select(-level_study)

total_enrollment_grad <- total_enrollment %>%
  filter(level_study==3)%>%
  select(-level_study)

Now if we examine our new tables they have the data nicely divided:

head(total_enrollment_combined)
head(total_enrollment_undergrad)
head(total_enrollment_grad)

We can now do a bit of data exploration toward answering the questions listed at the beginning of the document. Simple summary statistics like average enrollment will be somewhat limited in application because the institutions vary a lot in size. Averaging changes between institutions of different sizes is also a somewhat shaky approach – a 2 percent dip in a huge university might represent a much bigger change than a 20 percent dip in a tiny college. Therefore the best idea seems to be to combine all numbers across all the schools.

enrollment_combined_no_gender <- total_enrollment_combined %>%
  select(total_enrollment_2010,total_enrollment_2011,total_enrollment_2012,
         total_enrollment_2013,total_enrollment_2014,total_enrollment_2015,total_enrollment_2016,total_enrollment_2017,
         total_enrollment_2018)
combined_enrollment_figs <- colSums(enrollment_combined_no_gender)
years <- c(2010,2011,2012,2013,2014,2015,2016,2017,2018)
combined_enrollment_figs_df <- data.frame(years,combined_enrollment_figs)
combined_enrollment_viz <- ggplot(data=combined_enrollment_figs_df,aes(x=years,y=combined_enrollment_figs))+
  geom_point()+
  geom_line() +
  theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())+
  labs(title="Combined Enrollment (2010-2018)",x="Year",y="Combined enrollment (all institutions)")
ggsave(filename="combined_enrollment_viz.png",plot=combined_enrollment_viz,dpi=800)
Saving 7 x 7 in image
combined_enrollment_viz

Okay: the upshot is that combined (undergrad + grad) enrollment is going down.

Now let’s look at undergrad enrollment over the same period:

enrollment_undergrad_no_gender <- total_enrollment_undergrad %>%
  select(total_enrollment_2010,total_enrollment_2011,total_enrollment_2012,
         total_enrollment_2013,total_enrollment_2014,total_enrollment_2015,
         total_enrollment_2016,total_enrollment_2017,total_enrollment_2018)

undergrad_enrollment_figs <- colSums(enrollment_undergrad_no_gender)

years <- c(2010,2011,2012,2013,2014,2015,2016,2017,2018)
undergrad_enrollment_figs_df <- data.frame(years,undergrad_enrollment_figs)
undergrad_enrollment_viz <- ggplot(data=undergrad_enrollment_figs_df,aes(x=years,y=undergrad_enrollment_figs))+
  geom_point()+
  geom_line() +theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())+
  labs(title="Undergrad Enrollment (2010-2018)",x="Year",y="Undergrad enrollment (all institutions)")
ggsave(filename="undergrad_enrollment_viz.png",plot=undergrad_enrollment_viz,dpi=800)
Saving 7 x 7 in image
undergrad_enrollment_viz

The undergraduate trend follows the same trend as the combined trend (no surprises there).

It’s less important, but we can also take a look at graduate enrollment over the same period.

enrollment_grad_no_gender <- total_enrollment_grad %>%
  select(total_enrollment_2010,total_enrollment_2011,total_enrollment_2012,
         total_enrollment_2013,total_enrollment_2014,total_enrollment_2015,
         total_enrollment_2016,total_enrollment_2017,total_enrollment_2018)
grad_enrollment_figs <- colSums(enrollment_grad_no_gender)
years <- c(2010,2011,2012,2013,2014,2015,2016,2017,2018)
grad_enrollment_figs_df <- data.frame(years,grad_enrollment_figs)
grad_enrollment_viz <- ggplot(data=grad_enrollment_figs_df,aes(x=years,y=grad_enrollment_figs))+
  geom_point()+
  geom_line() +
  theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())+
  labs(title="Graduate Enrollment (2010-2018)",x="Year",y="Graduate enrollment (all institutions)")
ggsave(filename="grad_enrollment_viz.png",plot=grad_enrollment_viz,dpi=800)
Saving 7 x 7 in image
grad_enrollment_viz

If we juxtapose the three images, we see that the loss in overall enrollment seems to be driven by loss in undergraduate enrollment, and that the graduate enrollment rallied slightly starting in 2013. Since graduate enrollment represents about 10 percent of the combined enrollment, the rally in graduate enrollment does not balance out the diminishing undergraduate enrollment.

combined_enrollment_viz

undergrad_enrollment_viz

grad_enrollment_viz

The overall trend is: undergraduate enrollment is going down.

The EFFY survey data is split up by gender, so we can try to see if there are any interesting trends by gender.

enrollment_combined_men <- total_enrollment_combined %>%
  select(men_enrollment_2010,men_enrollment_2011,men_enrollment_2012,
         men_enrollment_2013,men_enrollment_2014,men_enrollment_2015,
         men_enrollment_2016,men_enrollment_2017,men_enrollment_2018)
combined_men_enrollment_figs <- colSums(enrollment_combined_men)
years <- c(2010,2011,2012,2013,2014,2015,2016,2017,2018)
combined_enrollment_men_figs_df <- data.frame(years,combined_men_enrollment_figs)
combined_enrollment_men_viz <- ggplot(data=combined_enrollment_men_figs_df,aes(x=years,y=combined_men_enrollment_figs))+
  geom_point()+
  geom_line() +
  labs(title="Combined Mens Enrollment (2010-2018)",x="Year",y="Combined mens enrollment (all institutions)")+
  theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())
ggsave(filename="combined_men_enrollment_viz.png",plot=combined_enrollment_men_viz,dpi=800)
Saving 7 x 7 in image
combined_enrollment_men_viz

Not exactly a shock: the combined mens enrollment is going down quite sharply (a bit more sharply than the mens+womens combined enrollment).

enrollment_combined_women <- total_enrollment_combined %>%
  select(women_enrollment_2010,women_enrollment_2011,women_enrollment_2012,
         women_enrollment_2013,women_enrollment_2014,women_enrollment_2015,
         women_enrollment_2016,women_enrollment_2017,women_enrollment_2018)
combined_women_enrollment_figs <- colSums(enrollment_combined_women)
years <- c(2010,2011,2012,2013,2014,2015,2016,2017,2018)
combined_enrollment_women_figs_df <- data.frame(years,combined_women_enrollment_figs)
combined_enrollment_women_viz <- ggplot(data=combined_enrollment_women_figs_df,aes(x=years,y=combined_women_enrollment_figs))+
  geom_point()+
  geom_line() +
  labs(title="Combined Womens Enrollment (2010-2018)",x="Year",y="Combined womens enrollment (all institutions)")+
  theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())
ggsave(filename="combined_women_enrollment_viz.png",plot=combined_enrollment_women_viz,dpi=800)
Saving 7 x 7 in image
combined_enrollment_women_viz

This gives us a clue that a slight recovery in enrollment has been driven by women returning to college before men.

Now we want this to include some other data: geographical region (this is from the HD data set) and discplines.

regions = select(df_hd_14,"university_id"=UNITID,"region_code"=OBEREG)
head(regions)

The university_id’s are familiar to us, and the region_code’s are as follows (this can be found on the Excel files included in the data download).

0 - US Service schools (such as West Point or the Merchant Marine Academy) 1 - New England (CT ME MA NH RI VT) 2 - Mid East (DE DC MD NJ NY PA) 3 - Great Lakes (IL IN MI OH WI) 4 - Plains (IA KS MN MO NE ND SD) 5 - Southeast (AL AR FL GA KY LA MS NC SC TN VA WV) 6 - Southwest (AZ NM OK TX) 7 - Rocky Mountains (CO ID MT UT WY) 8 - Far West (AK CA HI NV OR WA) 9 - Outlying areas (AS FM GU MH MP PR PW VI) -3 - Not available

Sidenote: it’s debatable if these regions are ideal. New England is well-defined and conventional, but, e.g., bundling Minnesota with Kansas instead of with Wisconsin is maybe debatable.

Now we want to combine the regions data with the enrollment data. The following code combines the enrollment data from the EFFY survey with the region codes so that we can analyze regional trends.

total_enrollment_regions <- total_enrollment %>%
  inner_join(regions, by = "university_id")
combined_enrollment_regions <- total_enrollment_regions %>%
  filter(level_study==999)


combined_no_gender_enrollment_regions <- combined_enrollment_regions %>%
  select(region_code,total_enrollment_2010,total_enrollment_2011,total_enrollment_2012,
         total_enrollment_2013,total_enrollment_2014,total_enrollment_2015,
         total_enrollment_2016,total_enrollment_2017,total_enrollment_2018)

We haven’t totally tidied the data, but it’s good enough for our purposes (the ideal tidied data table contains a single measurement in each row, so we wouldn’t have enrollment figures from multiple years in the same row). The following chunk serves to find the total enrollment per region as a function of the year.

combined_no_gender_enrollment_regions_by_regions <- combined_no_gender_enrollment_regions %>%
  group_by(region_code)%>%
  transmute("2010"=sum(total_enrollment_2010),
         "2011"=sum(total_enrollment_2011),
         "2012"=sum(total_enrollment_2012),
         "2013"=sum(total_enrollment_2013),
         "2014"=sum(total_enrollment_2014),
         "2015"=sum(total_enrollment_2015),
         "2016"=sum(total_enrollment_2016),
         "2017"=sum(total_enrollment_2017),
         "2018"=sum(total_enrollment_2018)) %>%
  distinct(region_code,.keep_all = TRUE) %>%
  arrange(region_code)
reshaped_regions_no_gender <- combined_no_gender_enrollment_regions_by_regions %>%
  gather('2010','2011','2012','2013','2014','2015','2016','2017','2018',key='year',value='enrollment')

Lets take a look at this new table: it contains only three variables (region_code, year, and enrollment), and each row represents a single enrollment figure.

head(reshaped_regions_no_gender)

Let’s get into the plotting of this data. First we make a dataframe.

reshaped_regions_no_gender_df <- data.frame(reshaped_regions_no_gender)
reshaped_regions_no_gender_df

The region code 0 stands for US Service Academies like West Point, where enrollment is basically constant (as well as being a tiny fraction of any of the other regions). The same thing is true for the outlying areas such as Guam. So we might as well filter it out

regions_no_gender_df <- 
  reshaped_regions_no_gender_df %>%
  filter(region_code != 0 & region_code != 9)
head(regions_no_gender_df)

Now we can create our list of objects.

labels_regions <-  c('New England (CT ME MA NH RI VT)', 
                     'Mid East (DE DC MD NJ NY PA)',
                     'Great Lakes (IL IN MI OH WI)', 
                     'Plains (IA KS MN MO NE ND SD)',             
                     'Southeast (AL AR FL GA KY LA MS NC SC TN VA WV)',
                     'Southwest (AZ NM OK TX)',
                     'Rocky Mountains (CO ID MT UT WY)', 
                     'Far West (AK CA HI NV OR WA)')

Let’s build a more concise set of labels:

labels_regions_concise <- c('New England', 
                            'Mid East',
                            'Great Lakes', 
                            'Plains', 
                            'Southeast',
                            'Southwest',
                            'Rocky Mountains', 
                            'Far West')

The following is a ggplot visualization.

regions_viz <- ggplot(data=regions_no_gender_df,aes(x=strtoi(year)-2000,y=enrollment/10^6,color=factor(region_code),group=factor(region_code),label=factor(region_code)))+
  geom_point()+
  geom_line(key_glyph="rect")+
  theme_light()+
  theme(legend.title = element_text(color="chocolate",size=16,face="bold"),
        legend.background = element_rect(fill="white",
                                  size=0.5, linetype="solid", 
                                  colour ="darkblue"),
        legend.key = element_rect(fill = "white", colour = "black"),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank())+
  scale_color_hue(name="Region",
                       labels=labels_regions_concise)+
  labs(y="Enrollment (in millions)",x="Year",title="Regional enrollment trends: 2010-2018")
ggsave(filename="regional_trends.png",plot=regions_viz,dpi=800)
Saving 7 x 7 in image
regions_viz

That gives us a picture of some of the trends.

New England is static.

Far West, after a dip in 2013, seems to be recovering.

Rocky Mountains is increasing at a modest but steady rate.

The Great Lakes region is decreasing.

=======DISCIPLINARY TRENDS========

Okay, next we would like to get some disciplinary information added in. Remember that these are stored in the completions tables:

head(df_completions_14)

The important columns here are UNITID (university ID code), CIPCODE (discplinary subject), CTOTALT (total completions in the discipline).

We have a list of CIPCODEs of the form xx.xxxx. The first two digits store the broad region (such as Biology or Education) and the last four indicate the specialty (such as Biochemistry or Wildlife Biology).

At first, we want to study broad trends, so we are only interested in the trends at the xx.-level. We need to split up the CIP code by the ‘.’ delimiter and then grab the first part.

completions_split_10 <- df_completions_10 %>%
  mutate(coarsecip = substr(CIPCODE,1,2),
         subcip = substr(CIPCODE,2,2))
completions_coarse_10 <- completions_split_10 %>%
  select(UNITID,coarsecip,CTOTALT)

Lets see if that worked:

head(completions_coarse_10)

Not too bad! But we only want the totals grouped by CIP – we don’t really care about the UNITID:

completions_coarse_by_cip_10 <- completions_coarse_10 %>%
  group_by(coarsecip) %>%
  summarize("2010" = sum(CTOTALT))
  completions_coarse_by_cip_10

That’s what we wanted: the total number of completed degrees by CIPCode. In the chunk below we have repeated this operation for each of the other years in our dataset.

completions_split_11 <- df_completions_11 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_11 <- completions_split_11 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2011" = sum(CTOTALT))
  
completions_split_12 <- df_completions_12 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_12 <- completions_split_12 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2012" = sum(CTOTALT))

completions_split_13 <- df_completions_13 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_13 <- completions_split_13 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2013" = sum(CTOTALT))

completions_split_14 <- df_completions_14 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_14 <- completions_split_14 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2014" = sum(CTOTALT))

completions_split_15 <- df_completions_15 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_15 <- completions_split_15 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2015" = sum(CTOTALT))

completions_split_16 <- df_completions_16 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_16 <- completions_split_16 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2016" = sum(CTOTALT))

completions_split_17 <- df_completions_17 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_17 <- completions_split_17 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2017" = sum(CTOTALT))

completions_split_18 <- df_completions_18 %>%
  mutate(coarsecip = substr(CIPCODE,1,2))
completions_coarse_by_cip_18 <- completions_split_18 %>%
  select(UNITID,coarsecip,CTOTALT) %>%
  group_by(coarsecip) %>%
  summarize("2018" = sum(CTOTALT))

We now have reasonably organized completion data for all the programs. The next step is to combine it into a single data frame.

We want the coarse_cip completion data to be organized into a single table.

all_completions_cip <- completions_coarse_by_cip_10 %>%
  merge(completions_coarse_by_cip_11) %>%
  merge(completions_coarse_by_cip_12) %>%
  merge(completions_coarse_by_cip_13) %>%
  merge(completions_coarse_by_cip_14) %>%
  merge(completions_coarse_by_cip_15) %>%
  merge(completions_coarse_by_cip_16) %>%
  merge(completions_coarse_by_cip_17) %>%
  merge(completions_coarse_by_cip_18)
all_completions_cip 

This is nice, but we’d really like to have the years as variables, in order to comply with the tidy data paradigm. (We could have done this earlier.)

reshaped_completions <- all_completions_cip %>%
  gather("2010","2011","2012","2013","2014","2015","2016","2017","2018",key="year",value="completions")
reshaped_completions

This is properly tidied coarse_cip completion data by year.

There is one that we would like to exclude: Cip code 99, which describes the grand total of all degree completions.

reshaped_completions <- reshaped_completions %>%
  filter(coarsecip != '99')

Now we can do a big facet graph, plotting for each CIPCODE a different line graph showing completions of degrees in that CIPCODE over the years 2010-2018.

WARNING: the first version is going to look very rough in RStudio. However the version that saves to the directory is not as cluttered.

bad_facets <- ggplot(data = reshaped_completions, aes(year, completions,group=1)) +
  geom_line(color = "steelblue", size = 1) +
  geom_point(color="steelblue") + 
  labs(title = "Degree completions by CIP code (2010-2018)",
       y = "Count of degree completions", x = "year") + 
  facet_wrap(~coarsecip)

ggsave(filename='bad_facets.png',plot=bad_facets,dpi=800)
Saving 7 x 7 in image
bad_facets

This gives us some good trends (09, 11, 14, 24, 51, 52) all seem to be increasing. There is one thing we can do to make it a bit more meaningful: swap out cip codes for names (which I’ve summarized because the CIPcode descriptions are sometimes lengthy).

codes_number <- c(1,3,4,5,9,10,11,12,13,14,15,16,19,22,23,24,25,26,27, 29,30,31,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54)
codes_chr <- as.character(codes_number)
cip_data <- data.frame("coarsecip" = codes_chr, "area" = c("agri","envsci","arch/urb","area studies","comms/journ","graph des.","comp.","beauty","educ","engns","technician","langs","human sci","law","lit","lib arts","library","bio","math","intel","gen studies","mgmt","phil","rel","chem","sci tech","psych","responder","admin","soc sci","trades","assorted tech","prod trade","air traff","art","health","bus/fin","history"))
cip_data

Let’s get the area names in there

reshaped_completions
reshaped_completions_areas <- reshaped_completions %>%
  merge(cip_data) %>%
  arrange(desc(coarsecip))
reshaped_completions_areas

Now lets do the same facet wrap:

discipline_viz <- ggplot(data = reshaped_completions_areas, aes(year, completions,group=1)) +
  geom_line(color = "steelblue", size = 1) +
  geom_point(color="steelblue") + 
  labs(title = "Degree completions by area (2010-2018)",
       y = "Count of degree completions", x = "year") +
  
  facet_wrap(~area)+
  theme_bw()+
  theme(panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks = element_blank())
ggsave(filename='discipline_graph.png',plot=discipline_viz,dpi=400)
Saving 7 x 7 in image
discipline_viz

This is nice (it is tiny in Rstudio but I recommend looking at the saved image discipline_graph.png in the directory), but it washes out a lot of the trends for the lower-enrollment areas. So we might want to look at specific disciplines.

Let’s create a new dataframe that tracks something very coarse: percent growth in a field over the time interval 2010-2018. We calculate this as 100*(change in completions)/(2010 completions).

head(reshaped_completions_areas)
net_completions <-  reshaped_completions_areas %>% 
  filter(year == 2010 | year == 2018) %>%
  spread(year, completions) 
colnames(net_completions) <- c('coarsecip','area','comps_2010','comps_2018')
net_completions <- net_completions %>%
  mutate(percent_change = 100*(comps_2018/comps_2010-1)) %>%
  arrange(desc(percent_change))
head(net_completions,10)

We see that the 10 most increasing areas are those listed above To clarify these abbreviations: sci tech covers basically any degree preparing for work as a technician in a lab, comp. is computer science, prod trade is degrees related to production like manufacturing, mgmt is management science, engns is engineering.

Some of these are not surprising (nobody can be shocked by bronze medal for computer areas); some are more surprising (liberal arts has a lot more completions despite a lot of press about the death of liberal arts degrees).

Answer to one of our questions: A comparatively small investment in educational resources could return major benefits; also more math and computer science resources.

The visualization below makes this a bit more striking. The red line represents a 0 percent change in the number of degree completions.

percents_viz <- ggplot(data=net_completions,aes(x=area,y=percent_change))+
  geom_point()+
  theme(axis.text.x = element_text(angle = -90,vjust=0.45),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        panel.background = element_blank())+
  geom_hline(yintercept=0,color='red')+
  labs(title="Net change in degree completion: 2010-2018",x='area',y='percent change in enrollment')
percents_viz

This is a bit cluttered: let’s pin down the 10 fastest-growing and 10 fastest-shrinking areas.

ten_fastest <- net_completions %>%
  arrange(desc(percent_change)) %>%
  slice(1:10)
#ten_fastest

ten_worst <- net_completions %>%
  arrange(percent_change) %>%
  slice(1:10)
#ten_worst

fastest_viz <- ggplot(data=ten_fastest,aes(x=area,y=percent_change))+
  geom_point()+
  theme(axis.text.x = element_text(angle = 30))+
  geom_hline(yintercept=0,color='red')+
  theme(axis.text.x = element_text(angle = -90,vjust=0.45),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        panel.background = element_blank())+
  labs(title="Fastest areas of growth: 2010-2018",x='area',y='percent (+) change in enrollment')+
  ggsave('fastest_viz.png',dpi=800)
Saving 7 x 7 in image
slowest_viz <- ggplot(data=ten_worst,aes(x=area,y=percent_change))+
  geom_point()+
  theme(axis.text.x = element_text(angle = 30))+
  theme(axis.text.x = element_text(angle = -90,vjust=0.45),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        panel.background = element_blank())+
  geom_hline(yintercept=0,color='red')+
  labs(title="Weakest areas of growth: 2010-2018",x='area',y='percent change in enrollment')+
  ggsave('weakest_viz.png',dpi=800)
fastest_viz

slowest_viz

We saw that there was some good growth in the mathematics area. Anyone who follows higher-educational news in mathematics will know that statistics is growing in popularity as a major course of study. However, the Completions survey does not provide a separate “Coarse” CIP for stats; it’s stuck within the completions for math majors. Let’s see if we can extract some useful information about the completion of statistics degrees.

Remember what the non-coarse completions data looked like:

head(df_completions_10)

We still want to break apart the CIPCODE but now we want to filter to the Coarse CIP 27 – this is where all the mathematics is. Here are the fine CIPCODES for Mathematics (I started converting the codes to character by hand before I remembered you can cast to character vectorwise)

subfields_names <- c("Mathematics, General",
                     "Algebra and Number Theory",
                     "Analysis and Functional Analysis",
                     "Topology and Foundations",
                     "Mathematics, Other",
                     "Applied Mathematics (General)",
                     "Computational Mathematics",
                     "Computational and Applied Mathematics",
                     "Financial Mathematics",
                     "Mathematical Biology",
                     "Applied Mathematics (Other)",
                     "Statistics",
                     "Math-stats and probability",
                     "Math-stats",
                     "Statistics (Other)",
                     "Math-stats (Other)")
subfields_codes <- c(27.0101,
                     27.0102,
                     27.0103,
                     27.0105,
                     27.019,
                     27.0301,
                     27.0303,
                     27.0304,
                     27.0305,
                     27.0306,
                     27.0399,
                     27.0501,
                     27.0502,
                     27.0503,
                     27.0599,
                     27.9999)
subfields_codes <- as.character(subfields_codes)
subfields_df <- data.frame("subfield"=subfields_names,"CIPCODE"=subfields_codes)
subfields_df

If like me you find the list of subfields kind of ridiculous and redundant, don’t worry, we will simplify it later on, after we have summarized a bit.

We have the split dataframes. Here the math completions will have coarsecip = 27 and the subcip will indicate what specialty the degree was in (like stats or math-econ).

completions_split_10 %>% 
  select(UNITID,CTOTALT,coarsecip) %>%
  filter(coarsecip == '27')

These are quite long, with separate completion data by gender and ethnic lines.

completions_split_10 %>%
  filter(coarsecip == '27') %>%
  group_by(CIPCODE) %>%
  summarise("2010"=sum(CTOTALT))
math_completions_10 <- completions_split_10 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2010'=sum(CTOTALT))

math_completions_11 <- completions_split_11 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2011'=sum(CTOTALT))

math_completions_12 <- completions_split_12 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2012'=sum(CTOTALT))

math_completions_13 <- completions_split_13 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2013'=sum(CTOTALT))

math_completions_14 <- completions_split_14 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2014'=sum(CTOTALT))

math_completions_15 <- completions_split_15 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2015'=sum(CTOTALT))

math_completions_16 <- completions_split_16 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2016'=sum(CTOTALT))

math_completions_17 <- completions_split_17 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2017'=sum(CTOTALT))

math_completions_18 <- completions_split_18 %>%
  filter(coarsecip == '27') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2018'=sum(CTOTALT))

Let’s take a peep:

math_completions_18

Now we have all the data for all the subfields for the years 2010-2018 stored in nine separate tables. Again we need to merge them

math_completions <- math_completions_10 %>%
  merge(math_completions_11,how='full') %>%
  merge(math_completions_12,how='full') %>%
  merge(math_completions_13,how='full') %>%
  merge(math_completions_14,how='full') %>%
  merge(math_completions_15,how='full') %>%
  merge(math_completions_16,how='full') %>%
  merge(math_completions_17,how='full') %>%
  merge(math_completions_18,how='full')
head(math_completions)

Now we’re cooking! We ust need to tidy up the data.

Let’s add in a column of all the subfield names, then tidy along the year axis.

math_completions <- math_completions %>%
  merge(subfields_df)
math_completions <- math_completions %>%
  gather("2010","2011","2012","2013","2014","2015","2016","2017","2018",key="year",value="completions")
math_completions

Before we visualize, it will be handy to have abbreviated names for each subfield.

subfields_df
abbr_math_subfield <- c('math',
                        'algebra',
                        'analysis',
                        'topology',
                        'mathother',
                        'applied',
                        'compmath',
                        'compmath+appl',
                        'finmath',
                        'mathbio',
                        'applmathoth',
                        'stat',
                        'mathstatprob',
                        'mathstat',
                        'statother',
                        'mathstatother')

Let’s update the subfields list with these abbreviated names:

subfields_df$abbrev <- abbr_math_subfield

In preparing this list, it jumps out at you how there are actually fewer real subfields: math, aaplied/computational math, stats.

Lets write a crude-subfield functions

crude_subfield <- function(t) {
  if (t %in%
      c('applied',
        'compmath',
        'compmath+appl',
        'finmath',
        'mathbio',
        'applmathoth')){
    'applied'
  }
  else if (t %in%
      c('math',
        'algebra',
        'analysis',
        'topology',
        'mathother')){
    'math'
  }
  else if (t %in% 
      c('stat',
        'mathstatprob',
        'mathstat',
        'statother',
        'mathstatother')){
    'stats'
  }
  }

Let’s test our function

crude_subfield('analysis')
[1] "math"
crude_subfield('statother')
[1] "stats"
crude_subfield('mathbio')
[1] "applied"

Now lets add a crude subfield column to the subfields dataframe

subfields_df_dec <- subfields_df %>% 
  mutate(crude = map_chr(abbrev,crude_subfield))
math_completions_dec <- math_completions %>%
  merge(subfields_df_dec, on = 'CIPCODE')
summarised_math_completions <- math_completions_dec %>%
  group_by(crude,year) %>%
  summarise('total_completions'=sum(completions))

Let’s visualize.

With the disclaimer that these are pretty crude bins, you can see strong growth across the board, with the applied fields (which includes computational math, applied math, and math bio) growing the fastest in recent years.

Let’s look at percent changes:

net_math_completions <-  math_completions_dec %>% 
  filter(year == 2010 | year == 2018) %>%
  select(year,completions,abbrev,crude)
net_math_completions <- net_math_completions %>%
  spread(year,completions)
colnames(net_math_completions)=c('subfield','crude_subfield','math_comps_10','math_comps_18')
net_math_completions <- net_math_completions %>%
  mutate(percent_change = 100*(math_comps_18/(math_comps_10+1)-1), net_change = math_comps_18-math_comps_10) %>%
  arrange(desc(percent_change))
net_math_completions$percent_change
 [1] 3400.00000  994.00000  834.37500  481.81818  375.51020  233.58209
 [7]  158.10664  121.80451  104.52088   53.04054   43.48758  -22.66667
v <- net_math_completions$percent_change
v[1] <- 100
net_math_completions$percent_change <- v
net_math_completions

We can also do the crude versiomn of this.

net_math_crude_completions <- net_math_completions %>%
  group_by(crude_subfield) %>%
  summarise(total_completions_10 = sum(math_comps_10),
            total_completions_18 = sum(math_comps_18))

net_math_crude_completions <- net_math_crude_completions %>%
  mutate(net_change = total_completions_18-total_completions_10,
         pct_change = (total_completions_18-total_completions_10)/total_completions_10)

Let’s visualize these growth rates:

pct_math_scatter <- ggplot(data=net_math_completions,aes(x=subfield,y=percent_change))+
  geom_point()+
  theme_bw()+
  theme(axis.text.x = element_text(angle = 90),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        axis.ticks = element_blank())+
  geom_hline(yintercept=0,color='red')+
  labs(title="Math subfield growth (by pct): 2010-2018",x='area',y='pct change in degree completions')+
  ggsave('math_fastest_viz.png',dpi=800)+
  coord_flip()
Saving 7 x 7 in image
pct_math_scatter

pct_math_scatter <- ggplot(data=net_math_completions,aes(x=subfield,y=net_change))+
  geom_point()+
  theme_bw()+
  theme(axis.text.x = element_text(angle = 90),
        panel.grid.major=element_blank(),
        panel.grid.minor=element_blank(),
        axis.ticks = element_blank())+
  geom_hline(yintercept=0,color='red')+
  labs(title="Math subfield growth (net): 2010-2018",x='area',y='net change in degree completions')+
  ggsave('math_fastest_viz.png',dpi=800)+
  coord_flip()
Saving 6.28 x 3.88 in image

pct_math_scatter

Let’s look at how the distributions have shifted over the period. (I know that pie charts have been deprecated )

Let’s do some tidying

Lets crudify it and switch to proportions.

ggsave("crude_bar_math.png",crude_bar_math,dpi=800)
Saving 7 x 7 in image
ggsave("crude_bar_math.png",crude_bar_math,dpi=800)
crude_bar_math

This shows that stats and applied fields are really growing within the math major, although we have already seen that there is a considerable amount of growth in general mathematics degrees. Any company that is trying to sell books and software to these students should look into publishing more applied and statistics titles.

======== UNFINISHED ANALYSIS OF OF COMPLETIONS DATA FOR COMPUTER SCIENCE ====

Below we sketch how to begin carrying out the same analysis for the Computer Science category (CoarseCIP: 11). The interested reader can update some of the commands and finish it.

subfields_names <- c("Gen CS","AI","IT","Informatics","CS (Other)",
                     "Gen program","Spec. program",
                     "Cert program", "Other program",
                     "Data processing","Info sci",
                     "Comp sys analyst","Data entry",
                     "Word proc","Other data entry",
                     "CS","Web page design","Data model",
                     "Graphics","Modelling","Software",
                     "Networks/Telecom","Network admin",
                     "LAN admin","Security","Webmaster",
                     "IT Project","Comp supp","IT Admin",
                     "Other CS Supp")
subfields_codes <- c(11.0101, 11.0102, 11.0103, 11.0104,
                     11.0199, 11.0201, 11.0202, 11.0203,
                     11.0299, 11.0301, 11.0401, 11.0501,
                     11.0601, 11.0602, 11.0699, 11.0701,
                     11.0801, 11.0802, 11.0803, 11.0804,
                     11.0899, 11.0901, 11.1001, 11.1002,
                     11.1003, 11.1004, 11.1005, 11.1006,
                     11.1099, 11.9999)
subfields_codes <- as.character(subfields_codes)
subfields_df <- data.frame("subfield"=subfields_names,"CIPCODE"=subfields_codes)
subfields_df

cs_completions_10 <- df_completions_10 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2010'=sum(CTOTALT))

cs_completions_11 <- df_completions_11 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2011'=sum(CTOTALT))

cs_completions_12 <- df_completions_12 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2012'=sum(CTOTALT))

cs_completions_13 <- df_completions_13 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2013'=sum(CTOTALT))

cs_completions_14 <- df_completions_14 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2014'=sum(CTOTALT))

cs_completions_15 <- df_completions_15 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2015'=sum(CTOTALT))

cs_completions_16 <- df_completions_16 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2016'=sum(CTOTALT))

cs_completions_17 <- df_completions_17 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2017'=sum(CTOTALT))

cs_completions_18 <- df_completions_18 %>%
  mutate(spltcip = substr(CIPCODE,1,2)) %>%
  filter(spltcip == '11') %>%
  select(CIPCODE, CTOTALT)%>%
  group_by(CIPCODE) %>%
  summarize('2018'=sum(CTOTALT))
cs_completions <- cs_completions_10 %>%
  merge(cs_completions_11,how='full') %>%
  merge(cs_completions_12,how='full') %>%
  merge(cs_completions_13,how='full') %>%
  merge(cs_completions_14,how='full') %>%
  merge(cs_completions_15,how='full') %>%
  merge(cs_completions_16,how='full') %>%
  merge(cs_completions_17,how='full') %>%
  merge(cs_completions_18,how='full')


cs_completions <- cs_completions %>%
  merge(subfields_df) %>%
  select(-CIPCODE)

cs_completions <- cs_completions %>%
  gather("2010","2011","2012","2013","2014","2015","2016","2017","2018",key="year",value="completions")
cs_completions

Finally we can visualize:

cs_subfield_viz <-  ggplot(data = cs_completions, aes(year, completions,group=1)) +
  geom_line(color = "steelblue", size = 1) +
  geom_point(color="steelblue") + 
  labs(title = "CS degrees by subfield (2010-2018)",
       y = "Count of degree completions", x = "year") + 
  facet_wrap(~subfield)
ggsave('discipline_viz.png',dpi=400)
cs_subfield_viz
net_cs_completions <-  cs_completions %>% 
  filter(year == 2010 | year == 2018) %>%
  spread(year, completions)
colnames(net_cs_completions) <- c("subfield","cs_comps_2010","cs_comps_2018")
net_cs_completions <- net_cs_completions %>%
  mutate(percent_change = 100*(cs_comps_2018/cs_comps_2010-1), net_change = cs_comps_2018-cs_comps_2010) %>%
  arrange(desc(percent_change))
net_cs_completions$percent_change
v <- net_cs_completions$percent_change
v[1] <- 100
net_cs_completions$percent_change <- v
net_cs_completions

Let’s visualize these growth rates:

pct_cs_scatter <- ggplot(data=net_cs_completions,aes(x=subfield,y=percent_change))+
  geom_text(aes(label=subfield))+
  theme(axis.text.x = element_text(angle = 90))+
  geom_hline(yintercept=0,color='red')+
  labs(title="CS subfield growth (by pct): 2010-2018",x='area',y='pct change in degree completions')+
  ggsave('cs_overall_pct_viz.png',dpi=400)+
  coord_flip()
pct_cs_scatter

pct_cs_scatter <- ggplot(data=net_cs_completions,aes(x=subfield,y=net_change))+
  theme(axis.text.x = element_text(angle = 90))+
  geom_hline(yintercept=0,color='red')+
  geom_label(aes(label=floor(net_change)))+
  labs(title="CS subfield growth (net): 2010-2018",x='area',y='net change in degree completions')+
  ggsave('cs_overall_net_viz.png',dpi=400)+
  coord_flip()
pct_cs_scatter

Let’s filter this down a bit

(best_net_cs <- net_cs_completions %>%
  arrange(desc(net_change)) %>%
  slice(1:10))

(best_pct_cs <- net_cs_completions %>%
  arrange(desc(percent_change)) %>%
  slice(1:10))
best_net_cs_scatter <- ggplot(data=best_net_cs,aes(x=subfield,y=percent_change))+
  geom_text(aes(label=subfield))+
  theme(axis.text.x = element_text(angle = 90))+
  geom_hline(yintercept=0,color='red')+
  labs(title="Best CS subfield growth (by net): 2010-2018",x='area',y='pct change in degree completions')+
  ggsave('cs_fastest_net_viz.png',dpi=400)+
  coord_flip()
best_net_cs_scatter

best_pct_cs_scatter <- ggplot(data=best_pct_cs,aes(x=subfield,y=percent_change))+
  geom_text(aes(label=subfield))+
  theme(axis.text.x = element_text(angle = 90))+
  geom_hline(yintercept=0,color='red')+
  labs(title="Best CS subfield growth (by pct): 2010-2018",x='area',y='pct change in degree completions')+
  ggsave('cs_fastest_pct_viz.png',dpi=400)+
  coord_flip()
best_pct_cs_scatter

It’s worth noting what the different codes are:

Data modeling includes Data Warehousing and Database Admin

Modelling includs Virtual Environments and Simulation

Many informatics degrees are budding into data science disciplines (e.g. at the University of Washington).

LS0tCnRpdGxlOiAiRW5yb2xsbWVudCBUcmVuZHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkhJR0hFUiBFRFVDQVRJT04gRVhQTE9SQVRPUlkgREFUQSBBTkFMWVNJUyBBTkQgVklTVUFMSVpBVElPTgoKPT09PT09PT09PT09CgpJTlRST0RVQ1RJT04KCj09PT09PT09PT09PQoKPT09PT09PT09PT09ClFVRVNUSU9OUyAKPT09PT09PT09PT09CgpUaGUgcXVlc3Rpb25zIHRoYXQgSSdtIHRyeWluZyB0byBhbnN3ZXIgd2l0aCB0aGlzIHByb2plY3QuCgoxLiBXaGF0IGFyZSB0aGUgdHJlbmRzIGluIGhpZ2hlci1lZHVjYXRpb25hbCBlbnJvbGxtZW50PwoyLiBBcmUgdGhlIHRyZW5kcyByZWdpb25hbCBvciBkaXNjaXBsaW5lIHNwZWNpZmljPwozLiBXaGVyZSBzaG91bGQgaGlnaGVyLWVkdWNhdGlvbiBjb250ZW50IGZpcm1zIGludmVzdCBpbiBzYWxlc2ZvcmNlIGFuZCBjb250ZW50IGRldmVsb3BtZW50PwoKCj09PT09PT09PT09PQpEQVRBIFNPVVJDRVMKPT09PT09PT09PT09CgpJIHVzZWQgZGF0YSBmcm9tIHRoZSBJbnRlZ3JhdGVkIFBvc3RzZWNvbmRhcnkgRWR1Y2F0aW9uIERhdGEgU3lzdGVtIChJUEVEUyksIHdoaWNoIGlzIGF2YWlsYWJsZSBhdCBodHRwczovL25jZXMuZWQuZ292L2lwZWRzL3VzZS10aGUtZGF0YS4gSVBFRFMgaGFzIGRhdGEgZnJvbSBhIHZhcmlldHkgb2Ygc3VydmV5cyBidXQgSSB1c2VkIHRoZSBmb2xsb3dpbmcgdGhyZWUgc3VydmV5cywgYWNjZXNzaWJsZSBmcm9tIHRoZSBkcm9wLWRvd24gbWVudSBvbiB0aGUgd2Vic2l0ZS4KCkZhbGwgZW5yb2xsbWVudCAoRUZGWSkuIFRoaXMgc3VydmV5IHJlcG9ydHMgdGhlIG51bWJlciBvZiBlbnJvbGxtZW50cyBpbiB0aGUgZmFsbCBzZW1lc3RlciBmb3IgZWFjaCByZXNwb25kaW5nIHNjaG9vbCwgY29sbGVnZSwgdW5pdmVyc2l0eSwgZXRjLiBXZSB1c2UgdGhpcyBmb3Igb3ZlcmFsbCB0cmVuZHMgYW5kIGFsc28gZm9yIHJlZ2lvbmFsIHRyZW5kcy4gIAoKQ29tcGxldGlvbnMuIFRoaXMgcmVwb3J0cyB0aGUgbnVtYmVyIG9mIGRlZ3JlZSBjb21wbGV0aW9ucyBmb3IgZWFjaCBpbnN0aXR1dGlvbiBieSBkZXBhcnRtZW50IChtYXRoLCBiaW9sb2d5LCBFbmdsaXNoLCBldGMuKSBhbmQgYWxzbyBieSBzdWJkaXNjaXBsaW5lIChzbyBmb3IgbWF0aGVtYXRpY3MgdGhlIGRhdGEgc3BsaXRzIGludG8gYXBwbGllZCBtYXRoLCBwdXJlIG1hdGgsIG1hdGgtZWNvbiwgZXRjLikuIFdlIHVzZSB0aGlzIHRvIGRldGVjdCB0cmVuZHMgYXQgdGhlIGxldmVsIG9mIGFjYWRlbWljIGRpc2NpcGxpbmUuCgpJbnN0aXR1dGlvbmFsIGNoYXJhY3RlcmlzdGljcyAtIGRpcmVjdG9yeSBpbmZvcm1hdGlvbiAoSEQpLiBUaGlzIHJlY29yZHMgdGhlIHJlZ2lvbiBmb3IgZWFjaCBzY2hvb2wuIFdlIHVzZSB0aGlzIHRvIGRldGVjdCByZWdpb25hbCB0cmVuZHMuIAoKPT09PT09PT09PT09CkNPTkNMVVNJT05TCj09PT09PT09PT09PQoKVGhlIGFuc3dlcnMgKHNlZSBiZWxvdyBmb3IgdGhlIGRhdGEgYW5hbHlzaXMgc3VwcG9ydGluZyB0aGVzZSk6CgoxLiBPdmVyYWxsIGVucm9sbG1lbnQgaXMgZG93biBmcm9tIDIwMTMtMjAxOC4gSWYgeW91IGRpdmlkZSB0aGUgZW5yb2xsbWVudCBkYXRhIGJ5IGdlbmRlciwgbWVuJ3MgZW5yb2xsbWVudCBoYXMgZGVjbGluZWQgbXVjaCBtb3JlIHNoYXJwbHkgdGhhbiB3b21lbidzIGVucm9sbG1lbnQuIFRoZSBkYXRhIHNob3dzIGEgc2xpZ2h0IHJlY292ZXJ5IGluIGVucm9sbG1lbnRzIGhhcHBlbmluZyBhcm91bmQgMjAxOCwgYnV0IHRoZSBtaXNzaW5nIDIwMTkgZGF0YSBtaWdodCBub3Qgc3VwcG9yIHRoaXMsIGFuZCB0aGVyZSBpcyB0aGUgbGFyZ2UgaXNzdWUgb2YgdGhlIHBhbmRlbWljIGFmZmVjdGluZyBlbnJvbGxtZW50IHN0YXJ0aW5nIGluIDIwMjDCoAoKU29tZXdoYXQgc3VycHJpc2luZ2x5LCB0aGVyZSBpcyBzdGlsbCBncm93dGggaW4gZGVncmVlIGNvbXBsZXRpb25zIGZvciBtYW55IGRpc2NpcGxpbmVzLCBpbiBzcGl0ZSBvZiB0aGUgb3ZlcmFsbCBkb3dud2FyZCB0cmVuZCBpbiBlbnJvbGxtZW50LiBJJ20gbm90IHN1cmUgaG93IHRvIGFjY291bnQgZm9yIHRoaXMsIGJ1dCBteSBndWVzcyBpcyB0aGF0IGluIGFueSB5ZWFyIGEgbGFyZ2UgbnVtYmVyIG9mIHN0dWRlbnRzIGVucm9sbCBpbiBjb2xsZWdlIHdobyB3aWxsIGV2ZW50dWFsbHkgZHJvcCBvdXQuIEl0IHNlZW1zIHBvc3NpYmxlIHRoYXQgZG93bndhcmQgdHJlbmQgaW4gZW5yb2xsbWVudCB0cmVuZHMgY291bGQgZWF0IGludG8gdGhpcyBncm91cCBvZiBzdHVkZW50cyBmaXJzdDogdGhleSBtaWdodCBoYXZlIGxlc3MgbW9uZXkgZm9yIGNvbGxlZ2UsIGJlIG9uIHRoZSBmZW5jZSBhYm91dCB3aGV0aGVyIHRvIGdvIHRvIGNvbGxlZ2UgdmVyc3VzIGVudGVyIHRoZSB3b3JrZm9yY2UsIGhhdmUgbGVzcyBwcmVwYXJhdGlvbiBmb3IgY29sbGVnZSwgZXRjLiBBbnkgZmFjdG9yIHRoYXQgd291bGQgbGF0ZXIgY29udHJpYnV0ZSB0byBhIHN0dWRlbnQgZHJvcHBpbmcgb3V0IG9mIGNvbGxlZ2UgbWlnaHQgcGxhdXNpYmx5IGxlYWQgdG8gdGhlbSBub3QgZW5yb2xsaW5nIGluIHRoZSBmaXJzdCBwbGFjZSwgcGFydGljdWxhcmx5IGR1cmluZyBhIHRpbWUgb2YgZWNvbm9taWMgdW5jZXJ0YWludHkgKG9yIGV2ZW4gZHVyaW5nIGEgdGltZSBvZiBlY29ub21pYyBncm93dGgsIHdoZW4gbm9uLWRlZ3JlZS1zcGVjaWZpYyBqb2JzIGFyZSBhdmFpbGFibGUpLiBJZiB0aGlzIGdyb3VwIGlzIGxhcmdlIGVub3VnaCwgdGhleSBjb3VsZCBhY2NvdW50IGZvciBkaW1pbmlzaGluZyBlbnJvbGxtZW50cyB3aGlsZSBvdGhlciBzdHVkZW50cywgbm90IGFzIGFwdCB0byBkcm9wIG91dCBvZiBjb2xsZWdlIGFmdGVyIGVucm9sbGluZywgZHJpdmUgYSBzbWFsbGVyIHVwd2FyZCB0cmVuZCBpbiBkZWdyZWUgY29tcGxldGlvbnMgZm9yIG1hbnkgYWNhZGVtaWMgc3ViZGlzY2lwbGluZXMuIAoKQW4gYWx0ZXJuYXRlIGV4cGxhbmF0aW9uIGlzIHRoYXQgc29tZSB1bml2ZXJzaXRpZXMgaGF2ZSBsb3dlcmVkIHRoZWlyIGFjYWRlbWljIHN0YW5kYXJkcyBpbiByZXNwb25zZSB0byBkaW1pbmlzaGluZyBlbnJvbGxtZW50cywgaW4gYSBiaWQgdG8gbG9zZSBmZXdlciBzdHVkZW50cyBiZWZvcmUgZGVncmVlIGNvbXBsZXRpb24uIFdpdGggdGhlIGdpdmVuIGRhdGEgSSBhbSB1bmNlcnRhaW4gb2YgaG93IHRvIGNob29zZSB3aGljaCBvZiB0aGVzZSAob3IgYm90aCwgb3IgbmVpdGhlcikgaXMgdGhlIGNvcnJlY3QgZXhwbGFuYXRpb24uIAoKMi4gVGhlIHRyZW5kcyBhcmUgYXBwYXJlbnRseSByZWdpb24gYW5kIGRpc2NpcGxpbmUtc3BlY2lmaWMuIFJlZ2lvbmFsbHkgdGhlIEZhciBXZXN0IHJlZ2lvbiBoYXMgc2VlbiBncm93dGggYW5kIHRoZSBHcmVhdCBMYWtlcyByZWdpb24gaGFzIHNlZW4gZGVjcmVhc2UgaW4gZW5yb2xsbWVudHMsIGp1c3QgdG8gbmFtZSB0d28gZXhhbXBsZXMuIEF0IHRoZSBkaXNjaXBsaW5hcnkgbGV2ZWwsIHRoZXJlIGhhcyBiZWVuIGRyYW1hdGljIGRlY2xpbmUgaW4gSGlzdG9yeSBkZWdyZWUgY29tcGxldGlvbnMgYWNjb21wYW5pZWQgYnkgYSByaXNlIGluIGNvbXBsZXRpb25zIG9mIHRlY2huaWNhbCBhbmQgQ29tcHV0ZXIgU2NpZW5jZSBkZWdyZWVzLiAKCjMuIFJlZ2lvbmFsbHksIHRoZSBTb3V0aGVhc3QgYW5kIEZhciBXZXN0IGFyZSBhbW9uZyB0aGUgZmFzdGVzdCBncm93aW5nIGFyZWFzLiBBbnkgcmVnaW9uYWwgc2FsZXNmb3JjZSBzaG91bGQgYmUgZGlyZWN0ZWQgdGhlcmUuIEF0IHRoZSBkaXNjaXBsaW5hcnkgbGV2ZWwsIENvbXB1dGVyIFNjaWVuY2UgYW5kIE1hdGhlbWF0aWNzIChpbmNsdWRpbmcgU3RhdGlzdGljcykgYXJlIGJvdGggZ3Jvd2luZyB2ZXJ5IHF1aWNrbHkuIFdpdGhpbiB0aGUgTWF0aGVtYXRpY3MgZGVncmVlIGNvbXBsZXRpb24gZGF0YSB3ZSBzZWUgYSB0cmVuZCB0b3dhcmQgYXBwbGllZCBtYXRoZW1hdGljcyBhbmQgc3RhdGlzdGljcywgYm90aCBhcmVhcyB3aGVyZSBjb250ZW50IGNyZWF0aW9uIHNob3VsZCBiZSBkaXJlY3RlZC4gVGhlcmUgYXJlIHNldmVyYWwgb3RoZXIgZGlzY2lwbGluZXMgdGhhdCBoYXZlIHNlZW4gc3Ryb25nIGdyb3d0aCwgcGFydGljdWxhcmx5IHNjaWVudGlmaWMgdGVjaG5pY2lhbiBkZWdyZWVzLiAKCj09PT09PT09PT09PQpEQVRBIEFOQUxZU0lTCj09PT09PT09PT09PQoKSGVyZSB3ZSBsb2FkIGEgZmV3IHN0YW5kYXJkIHRvb2xzIGZvciB3b3JraW5nIHdpdGggZGF0YWZyYW1lcyBhbmQgdmlzdWFsaXphdGlvbnMgaW4gUi4gCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocmVzaGFwZTIpCmBgYAoKRmlyc3QsIHdlIHdhbnQgdG8gbG9hZCB0aGUgZGF0YSAoc3RvcmVkIG9uIHRoZSBkaXNrIGFzIC5jc3YgZmlsZXMpIGludG8gc29tZSBkYXRhIGZyYW1lcy4gV2UgZ3JhYiB0aGUgRUZGWSBhbmQgQ29tcGxldGlvbnMgc3VydmV5cyBmb3IgZWFjaCBvZiB0aGUgeWVhcnMgMjAxMC0yMDE4LiBXZSBvbmx5IG5lZWQgb25lIGluc3RhbmNlIG9mIHRoZSBIRCBzdXJ2ZXkgYmVjYXVzZSB0aGlzIHN1cnZleSBvbmx5IGNvbnRhaW5zIHJlZ2lvbmFsIGNoYXJhY3RlcmlzdGljcy4gCgpgYGB7cn0KZGZfZWZmeV8xOCA8LSByZWFkX2NzdignZWZmeTIwMTguY3N2JykKZGZfZWZmeV8xNyA8LSByZWFkX2NzdignZWZmeTIwMTcuY3N2JykKZGZfZWZmeV8xNiA8LSByZWFkX2NzdignZWZmeTIwMTYuY3N2JykKZGZfZWZmeV8xNSA8LSByZWFkX2NzdignZWZmeTIwMTUuY3N2JykKZGZfZWZmeV8xNCA8LSByZWFkX2NzdignZWZmeTIwMTQuY3N2JykKZGZfZWZmeV8xMyA8LSByZWFkX2NzdignZWZmeTIwMTMuY3N2JykKZGZfZWZmeV8xMiA8LSByZWFkX2NzdignZWZmeTIwMTIuY3N2JykKZGZfZWZmeV8xMSA8LSByZWFkX2NzdignZWZmeTIwMTEuY3N2JykKZGZfZWZmeV8xMCA8LSByZWFkX2NzdignZWZmeTIwMTAuY3N2JykKCmRmX2NvbXBsZXRpb25zXzE4ID0gcmVhZF9jc3YoJ2MyMDE4X2EuY3N2JykKZGZfY29tcGxldGlvbnNfMTcgPSByZWFkX2NzdignYzIwMTdfYS5jc3YnKQpkZl9jb21wbGV0aW9uc18xNiA9IHJlYWRfY3N2KCdjMjAxNl9hLmNzdicpCmRmX2NvbXBsZXRpb25zXzE1ID0gcmVhZF9jc3YoJ2MyMDE1X2EuY3N2JykKZGZfY29tcGxldGlvbnNfMTQgPSByZWFkX2NzdignYzIwMTRfYS5jc3YnKQpkZl9jb21wbGV0aW9uc18xMyA9IHJlYWRfY3N2KCdjMjAxM19hLmNzdicpCmRmX2NvbXBsZXRpb25zXzEyID0gcmVhZF9jc3YoJ2MyMDEyX2EuY3N2JykKZGZfY29tcGxldGlvbnNfMTEgPSByZWFkX2NzdignYzIwMTFfYS5jc3YnKQpkZl9jb21wbGV0aW9uc18xMCA9IHJlYWRfY3N2KCdjMjAxMF9hLmNzdicpCgpkZl9oZF8xNCA8LSByZWFkX2NzdignaGQyMDE0LmNzdicpCmBgYAoKVGhlc2UgYXJlIGZhaXJseSBsYXJnZSB0YWJsZXMuIEZvciBleGFtcGxlLCBkZl9lZmZ5IGhhcyAxNjY4OCByb3dzIGFuZCBkZl9oZCBoYXMgNzY4Ny4gTGV0cyB0YWtlIGEgcGVlazogCmBgYHtyfQpoZWFkKGRmX2VmZnlfMTQpCmhlYWQoZGZfaGRfMTQpCmhlYWQoZGZfY29tcGxldGlvbnNfMTQpCmBgYAoKVGhlIGRvY3VtZW50YXRpb24gZm9yIHRoZSBkYXRhIGZpbGVzIGV4cGxhaW5zIHRoZSBzb21ld2hhdCBjcnlwdGljIGFiYnJldmlhdGlvbnMgdXNlZCBmb3IgY29sdW1uIGxhYmVscy4gRm9yIHRoZSBFRkZZIHN1cnZleXMsIHRoZSByZWFsbHkgaW1wb3J0YW50IGNvbHVtbnMgYXJlIFVOSVRJRCAoaW5zdGl0dXRpb25hbCBJRCBudW1iZXIpLCBFRllUT1RMVCAoZ3JhbmQgdG90YWwgZmFsbCBlbnJvbGxtZW50KSwgRUZZVE9UTE0gKHRvdGFsIG1lbiksIEVGVFlUT1RMVyAodG90YWwgd29tZW4pLCBMU1RVRFkgKGxldmVsIG9mIHN0dWR5IC0tIHVuZGVyZ3JhZHVhdGUgMSwgZ3JhZHVhdGUgMywgb3IgY29tYmluZWQgOTk5KS4gCgpMZXQncyBnZXQgc29tZSBmaWd1cmVzIGZyb20gc29tZSBvZiB0aGVzZSB0YWJsZXMuIAoKYGBge3J9CmVucm9sbG1lbnRfMjAxOCA8LXNlbGVjdChkZl9lZmZ5XzE4LFVOSVRJRCwgRUZZVE9UTFQsRUZZVE9UTE0sRUZZVE9UTFcsTFNUVURZKQplbnJvbGxtZW50XzIwMTcgPC1zZWxlY3QoZGZfZWZmeV8xNyxVTklUSUQsIEVGWVRPVExULEVGWVRPVExNLEVGWVRPVExXLExTVFVEWSkKZW5yb2xsbWVudF8yMDE2IDwtc2VsZWN0KGRmX2VmZnlfMTYsVU5JVElELCBFRllUT1RMVCxFRllUT1RMTSxFRllUT1RMVyxMU1RVRFkpCmVucm9sbG1lbnRfMjAxNSA8LXNlbGVjdChkZl9lZmZ5XzE1LFVOSVRJRCwgRUZZVE9UTFQsRUZZVE9UTE0sRUZZVE9UTFcsTFNUVURZKQplbnJvbGxtZW50XzIwMTQgPC1zZWxlY3QoZGZfZWZmeV8xNCxVTklUSUQsIEVGWVRPVExULEVGWVRPVExNLEVGWVRPVExXLExTVFVEWSkKZW5yb2xsbWVudF8yMDEzIDwtc2VsZWN0KGRmX2VmZnlfMTMsVU5JVElELCBFRllUT1RMVCxFRllUT1RMTSxFRllUT1RMVyxMU1RVRFkpCmVucm9sbG1lbnRfMjAxMiA8LXNlbGVjdChkZl9lZmZ5XzEyLFVOSVRJRCwgRUZZVE9UTFQsRUZZVE9UTE0sRUZZVE9UTFcsTFNUVURZKQplbnJvbGxtZW50XzIwMTEgPC1zZWxlY3QoZGZfZWZmeV8xMSxVTklUSUQsIEVGWVRPVExULEVGWVRPVExNLEVGWVRPVExXLExTVFVEWSkKZW5yb2xsbWVudF8yMDEwIDwtc2VsZWN0KGRmX2VmZnlfMTAsVU5JVElELCBFRllUT1RMVCxFRllUT1RMTSxFRllUT1RMVyxMU1RVRFkpCmBgYAoKTGV0cyByZW5hbWUgc29tZSBvZiB0aGUgZGF0YSBpbiB0aGVzZSB0YWJsZXMKYGBge3J9CmVucm9sbG1lbnRfMjAxOCA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDE4LCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwgInRvdGFsX2Vucm9sbG1lbnRfMjAxOCI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTgiPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDE4Ij1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKCmVucm9sbG1lbnRfMjAxNyA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDE3LCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwgInRvdGFsX2Vucm9sbG1lbnRfMjAxNyI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTciPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDE3Ij1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKCmVucm9sbG1lbnRfMjAxNiA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDE2LCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwgInRvdGFsX2Vucm9sbG1lbnRfMjAxNiI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTYiPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDE2Ij1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKCmVucm9sbG1lbnRfMjAxNSA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDE1LCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwgInRvdGFsX2Vucm9sbG1lbnRfMjAxNSI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTUiPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDE1Ij1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKCmVucm9sbG1lbnRfMjAxNCA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDE0LCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwgInRvdGFsX2Vucm9sbG1lbnRfMjAxNCI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTQiPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDE0Ij1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKZW5yb2xsbWVudF8yMDEzIDwtIHJlbmFtZShlbnJvbGxtZW50XzIwMTMsInVuaXZlcnNpdHlfaWQiID0gVU5JVElELCJ0b3RhbF9lbnJvbGxtZW50XzIwMTMiPUVGWVRPVExULCJtZW5fZW5yb2xsbWVudF8yMDEzIj1FRllUT1RMTSwgIndvbWVuX2Vucm9sbG1lbnRfMjAxMyI9RUZZVE9UTFcsImxldmVsX3N0dWR5Ij1MU1RVRFkpCmVucm9sbG1lbnRfMjAxMiA8LSByZW5hbWUoZW5yb2xsbWVudF8yMDEyLCJ1bml2ZXJzaXR5X2lkIiA9IFVOSVRJRCwidG90YWxfZW5yb2xsbWVudF8yMDEyIj1FRllUT1RMVCwibWVuX2Vucm9sbG1lbnRfMjAxMiI9RUZZVE9UTE0sICJ3b21lbl9lbnJvbGxtZW50XzIwMTIiPUVGWVRPVExXLCJsZXZlbF9zdHVkeSI9TFNUVURZKQplbnJvbGxtZW50XzIwMTEgPC0gcmVuYW1lKGVucm9sbG1lbnRfMjAxMSwidW5pdmVyc2l0eV9pZCIgPSBVTklUSUQsInRvdGFsX2Vucm9sbG1lbnRfMjAxMSI9RUZZVE9UTFQsIm1lbl9lbnJvbGxtZW50XzIwMTEiPUVGWVRPVExNLCAid29tZW5fZW5yb2xsbWVudF8yMDExIj1FRllUT1RMVywibGV2ZWxfc3R1ZHkiPUxTVFVEWSkKZW5yb2xsbWVudF8yMDEwIDwtIHJlbmFtZShlbnJvbGxtZW50XzIwMTAsInVuaXZlcnNpdHlfaWQiID0gVU5JVElELCJ0b3RhbF9lbnJvbGxtZW50XzIwMTAiPUVGWVRPVExULCJtZW5fZW5yb2xsbWVudF8yMDEwIj1FRllUT1RMTSwgIndvbWVuX2Vucm9sbG1lbnRfMjAxMCI9RUZZVE9UTFcsImxldmVsX3N0dWR5Ij1MU1RVRFkpCmBgYApMZXQncyB0YWtlIGEgZ2FuZGVyIGF0IHRoZXNlIHJlbGFiZWxlZCBkYXRhIHNldHMgCmBgYHtyfQpoZWFkKGVucm9sbG1lbnRfMjAxOCkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIGVucm9sbG1lbnQgZGF0YWZyYW1lcyByZXByZXNlbnQgZWFjaCBjb2xsZWdlIHRocmVlIHRpbWVzLCBzcGxpdCB1cCBieSBsZXZlbCBvZiBzdHVkeS4gKFdlIGNvdWxkIGZ1cnRoZXIgdGlkeSB0aGUgZGF0YSBzbyB0aGF0IGl0IHdhcyBzcGxpdCB1cCBieSBsZXZlbCBvZiBzdHVkeSBBTkQgZ2VuZGVyLCBidXQgd2UndmUgbGVmdCBpdCB0aGlzIHdheSBmb3Igbm93LikKClRoZSBuZXh0IHN0ZXAgaXMgdG8gY29tYmluZSBhbGwgdGhlIGVucm9sbG1lbnQgZGF0YSBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUuIFdlIGFjY29tcGxpc2ggdGhpcyB3aXRoIGEgcGlwZWxpbmUgb2Ygam9pbnMgb24gdGhlIHVuaXZlcnNpdHkgaWQgYW5kIGxldmVsIG9mIHN0dWR5LiBUaGUgaWRlYSBpcyB0aGF0IGluIHRoZSB0b3RhbF9lbnJvbGxtZW50IHRhYmxlIGVhY2ggdW5pdmVyc2l0eSBpcyByZXByZXNlbnRlZCBieSB0aHJlZSByb3dzOiBvbmUgd2l0aCB1bmRlcmdyYWR1YXRlIGVucm9sbG1lbnQsIG9uZSB3aXRoIGdyYWR1YXRlIGVucm9sbG1lbnQsIGFuZCBvbmUgd2l0aCBjb21iaW5lZCBlbnJvbGxtZW50LiBFYWNoIHJvdyBoYXMgZGF0YSBmb3IgdGhlIDkgeWVhcnMgaW4gcXVlc3Rpb24gKDIwMTAtMjAxOCksIHdoaWNoIGlzIGZ1cnRoZXIgc3BsaXQgdXAgYnkgZ2VuZGVyLgoKYGBge3J9CnRvdGFsX2Vucm9sbG1lbnQgPC0gZW5yb2xsbWVudF8yMDEwICU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDExLGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSAlPiUKICBpbm5lcl9qb2luKGVucm9sbG1lbnRfMjAxMiwgYnkgPWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDEzLGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDE0LGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDE1LGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSAlPiUKICBpbm5lcl9qb2luKGVucm9sbG1lbnRfMjAxNiwgYnkgPWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDE3LGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKSU+JQogIGlubmVyX2pvaW4oZW5yb2xsbWVudF8yMDE4LGJ5PWMoInVuaXZlcnNpdHlfaWQiLCJsZXZlbF9zdHVkeSIpKQpoZWFkKHRvdGFsX2Vucm9sbG1lbnQpCmBgYAoKVGhpcyB0YWJsZSBpcyBxdWl0ZSBuaWNlIC0tIGl0IGNvbnRhaW5zIHRoZSBkYXRhIGZvciBlYWNoIHVuaXZlcnNpdHkuIExldCdzIHNwbGl0IGl0IHVwIGludG8gY29tYmluZWQvdW5kZXJncmFkL2dyYWQgdGFibGVzLiBJIG1haW5seSBkaWQgdGhpcyBqdXN0IGFzIGFuIGV4ZXJjaXNlIGluIHBpcGVsaW5lIGFuZCBmaWx0ZXJpbmcuIAoKYGBge3J9Cgp0b3RhbF9lbnJvbGxtZW50X2NvbWJpbmVkIDwtIHRvdGFsX2Vucm9sbG1lbnQgJT4lCiAgZmlsdGVyKGxldmVsX3N0dWR5PT05OTkpICU+JQogIHNlbGVjdCgtbGV2ZWxfc3R1ZHkpCgp0b3RhbF9lbnJvbGxtZW50X3VuZGVyZ3JhZCA8LSB0b3RhbF9lbnJvbGxtZW50ICU+JQogIGZpbHRlcihsZXZlbF9zdHVkeT09MSklPiUKICBzZWxlY3QoLWxldmVsX3N0dWR5KQoKdG90YWxfZW5yb2xsbWVudF9ncmFkIDwtIHRvdGFsX2Vucm9sbG1lbnQgJT4lCiAgZmlsdGVyKGxldmVsX3N0dWR5PT0zKSU+JQogIHNlbGVjdCgtbGV2ZWxfc3R1ZHkpCmBgYAoKTm93IGlmIHdlIGV4YW1pbmUgb3VyIG5ldyB0YWJsZXMgdGhleSBoYXZlIHRoZSBkYXRhIG5pY2VseSBkaXZpZGVkOiAKYGBge3J9CmhlYWQodG90YWxfZW5yb2xsbWVudF9jb21iaW5lZCkKaGVhZCh0b3RhbF9lbnJvbGxtZW50X3VuZGVyZ3JhZCkKaGVhZCh0b3RhbF9lbnJvbGxtZW50X2dyYWQpCmBgYAoKCgoKV2UgY2FuIG5vdyBkbyBhIGJpdCBvZiBkYXRhIGV4cGxvcmF0aW9uIHRvd2FyZCBhbnN3ZXJpbmcgdGhlIHF1ZXN0aW9ucyBsaXN0ZWQgYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgZG9jdW1lbnQuIFNpbXBsZSBzdW1tYXJ5IHN0YXRpc3RpY3MgbGlrZSBhdmVyYWdlIGVucm9sbG1lbnQgd2lsbCBiZSBzb21ld2hhdCBsaW1pdGVkIGluIGFwcGxpY2F0aW9uIGJlY2F1c2UgdGhlIGluc3RpdHV0aW9ucyB2YXJ5IGEgbG90IGluIHNpemUuIEF2ZXJhZ2luZyBjaGFuZ2VzIGJldHdlZW4gaW5zdGl0dXRpb25zIG9mIGRpZmZlcmVudCBzaXplcyBpcyBhbHNvIGEgc29tZXdoYXQgc2hha3kgYXBwcm9hY2ggLS0gYSAyIHBlcmNlbnQgZGlwIGluIGEgaHVnZSB1bml2ZXJzaXR5IG1pZ2h0IHJlcHJlc2VudCBhIG11Y2ggYmlnZ2VyIGNoYW5nZSB0aGFuIGEgMjAgcGVyY2VudCBkaXAgaW4gYSB0aW55IGNvbGxlZ2UuIFRoZXJlZm9yZSB0aGUgYmVzdCBpZGVhIHNlZW1zIHRvIGJlIHRvIGNvbWJpbmUgYWxsIG51bWJlcnMgYWNyb3NzIGFsbCB0aGUgc2Nob29scy4gCgoKYGBge3J9CmVucm9sbG1lbnRfY29tYmluZWRfbm9fZ2VuZGVyIDwtIHRvdGFsX2Vucm9sbG1lbnRfY29tYmluZWQgJT4lCiAgc2VsZWN0KHRvdGFsX2Vucm9sbG1lbnRfMjAxMCx0b3RhbF9lbnJvbGxtZW50XzIwMTEsdG90YWxfZW5yb2xsbWVudF8yMDEyLAogICAgICAgICB0b3RhbF9lbnJvbGxtZW50XzIwMTMsdG90YWxfZW5yb2xsbWVudF8yMDE0LHRvdGFsX2Vucm9sbG1lbnRfMjAxNSx0b3RhbF9lbnJvbGxtZW50XzIwMTYsdG90YWxfZW5yb2xsbWVudF8yMDE3LAogICAgICAgICB0b3RhbF9lbnJvbGxtZW50XzIwMTgpCmNvbWJpbmVkX2Vucm9sbG1lbnRfZmlncyA8LSBjb2xTdW1zKGVucm9sbG1lbnRfY29tYmluZWRfbm9fZ2VuZGVyKQp5ZWFycyA8LSBjKDIwMTAsMjAxMSwyMDEyLDIwMTMsMjAxNCwyMDE1LDIwMTYsMjAxNywyMDE4KQpjb21iaW5lZF9lbnJvbGxtZW50X2ZpZ3NfZGYgPC0gZGF0YS5mcmFtZSh5ZWFycyxjb21iaW5lZF9lbnJvbGxtZW50X2ZpZ3MpCmNvbWJpbmVkX2Vucm9sbG1lbnRfdml6IDwtIGdncGxvdChkYXRhPWNvbWJpbmVkX2Vucm9sbG1lbnRfZmlnc19kZixhZXMoeD15ZWFycyx5PWNvbWJpbmVkX2Vucm9sbG1lbnRfZmlncykpKwogIGdlb21fcG9pbnQoKSsKICBnZW9tX2xpbmUoKSArCiAgdGhlbWVfYncoKSsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSkrCiAgbGFicyh0aXRsZT0iQ29tYmluZWQgRW5yb2xsbWVudCAoMjAxMC0yMDE4KSIseD0iWWVhciIseT0iQ29tYmluZWQgZW5yb2xsbWVudCAoYWxsIGluc3RpdHV0aW9ucykiKQpnZ3NhdmUoZmlsZW5hbWU9ImNvbWJpbmVkX2Vucm9sbG1lbnRfdml6LnBuZyIscGxvdD1jb21iaW5lZF9lbnJvbGxtZW50X3ZpeixkcGk9ODAwKQpjb21iaW5lZF9lbnJvbGxtZW50X3ZpegpgYGAKCk9rYXk6IHRoZSB1cHNob3QgaXMgdGhhdCBjb21iaW5lZCAodW5kZXJncmFkICsgZ3JhZCkgZW5yb2xsbWVudCBpcyBnb2luZyBkb3duLiAKCk5vdyBsZXQncyBsb29rIGF0IHVuZGVyZ3JhZCBlbnJvbGxtZW50IG92ZXIgdGhlIHNhbWUgcGVyaW9kOiAKCmBgYHtyfQplbnJvbGxtZW50X3VuZGVyZ3JhZF9ub19nZW5kZXIgPC0gdG90YWxfZW5yb2xsbWVudF91bmRlcmdyYWQgJT4lCiAgc2VsZWN0KHRvdGFsX2Vucm9sbG1lbnRfMjAxMCx0b3RhbF9lbnJvbGxtZW50XzIwMTEsdG90YWxfZW5yb2xsbWVudF8yMDEyLAogICAgICAgICB0b3RhbF9lbnJvbGxtZW50XzIwMTMsdG90YWxfZW5yb2xsbWVudF8yMDE0LHRvdGFsX2Vucm9sbG1lbnRfMjAxNSwKICAgICAgICAgdG90YWxfZW5yb2xsbWVudF8yMDE2LHRvdGFsX2Vucm9sbG1lbnRfMjAxNyx0b3RhbF9lbnJvbGxtZW50XzIwMTgpCgp1bmRlcmdyYWRfZW5yb2xsbWVudF9maWdzIDwtIGNvbFN1bXMoZW5yb2xsbWVudF91bmRlcmdyYWRfbm9fZ2VuZGVyKQoKeWVhcnMgPC0gYygyMDEwLDIwMTEsMjAxMiwyMDEzLDIwMTQsMjAxNSwyMDE2LDIwMTcsMjAxOCkKdW5kZXJncmFkX2Vucm9sbG1lbnRfZmlnc19kZiA8LSBkYXRhLmZyYW1lKHllYXJzLHVuZGVyZ3JhZF9lbnJvbGxtZW50X2ZpZ3MpCnVuZGVyZ3JhZF9lbnJvbGxtZW50X3ZpeiA8LSBnZ3Bsb3QoZGF0YT11bmRlcmdyYWRfZW5yb2xsbWVudF9maWdzX2RmLGFlcyh4PXllYXJzLHk9dW5kZXJncmFkX2Vucm9sbG1lbnRfZmlncykpKwogIGdlb21fcG9pbnQoKSsKICBnZW9tX2xpbmUoKSArdGhlbWVfYncoKSsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSkrCiAgbGFicyh0aXRsZT0iVW5kZXJncmFkIEVucm9sbG1lbnQgKDIwMTAtMjAxOCkiLHg9IlllYXIiLHk9IlVuZGVyZ3JhZCBlbnJvbGxtZW50IChhbGwgaW5zdGl0dXRpb25zKSIpCmdnc2F2ZShmaWxlbmFtZT0idW5kZXJncmFkX2Vucm9sbG1lbnRfdml6LnBuZyIscGxvdD11bmRlcmdyYWRfZW5yb2xsbWVudF92aXosZHBpPTgwMCkKdW5kZXJncmFkX2Vucm9sbG1lbnRfdml6CmBgYAoKVGhlIHVuZGVyZ3JhZHVhdGUgdHJlbmQgZm9sbG93cyB0aGUgc2FtZSB0cmVuZCBhcyB0aGUgY29tYmluZWQgdHJlbmQgKG5vIHN1cnByaXNlcyB0aGVyZSkuIAoKSXQncyBsZXNzIGltcG9ydGFudCwgYnV0IHdlIGNhbiBhbHNvIHRha2UgYSBsb29rIGF0IGdyYWR1YXRlIGVucm9sbG1lbnQgb3ZlciB0aGUgc2FtZSBwZXJpb2QuIApgYGB7cn0KZW5yb2xsbWVudF9ncmFkX25vX2dlbmRlciA8LSB0b3RhbF9lbnJvbGxtZW50X2dyYWQgJT4lCiAgc2VsZWN0KHRvdGFsX2Vucm9sbG1lbnRfMjAxMCx0b3RhbF9lbnJvbGxtZW50XzIwMTEsdG90YWxfZW5yb2xsbWVudF8yMDEyLAogICAgICAgICB0b3RhbF9lbnJvbGxtZW50XzIwMTMsdG90YWxfZW5yb2xsbWVudF8yMDE0LHRvdGFsX2Vucm9sbG1lbnRfMjAxNSwKICAgICAgICAgdG90YWxfZW5yb2xsbWVudF8yMDE2LHRvdGFsX2Vucm9sbG1lbnRfMjAxNyx0b3RhbF9lbnJvbGxtZW50XzIwMTgpCmdyYWRfZW5yb2xsbWVudF9maWdzIDwtIGNvbFN1bXMoZW5yb2xsbWVudF9ncmFkX25vX2dlbmRlcikKeWVhcnMgPC0gYygyMDEwLDIwMTEsMjAxMiwyMDEzLDIwMTQsMjAxNSwyMDE2LDIwMTcsMjAxOCkKZ3JhZF9lbnJvbGxtZW50X2ZpZ3NfZGYgPC0gZGF0YS5mcmFtZSh5ZWFycyxncmFkX2Vucm9sbG1lbnRfZmlncykKZ3JhZF9lbnJvbGxtZW50X3ZpeiA8LSBnZ3Bsb3QoZGF0YT1ncmFkX2Vucm9sbG1lbnRfZmlnc19kZixhZXMoeD15ZWFycyx5PWdyYWRfZW5yb2xsbWVudF9maWdzKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fbGluZSgpICsKICB0aGVtZV9idygpKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKSsKICBsYWJzKHRpdGxlPSJHcmFkdWF0ZSBFbnJvbGxtZW50ICgyMDEwLTIwMTgpIix4PSJZZWFyIix5PSJHcmFkdWF0ZSBlbnJvbGxtZW50IChhbGwgaW5zdGl0dXRpb25zKSIpCmdnc2F2ZShmaWxlbmFtZT0iZ3JhZF9lbnJvbGxtZW50X3Zpei5wbmciLHBsb3Q9Z3JhZF9lbnJvbGxtZW50X3ZpeixkcGk9ODAwKQpncmFkX2Vucm9sbG1lbnRfdml6CmBgYAoKSWYgd2UganV4dGFwb3NlIHRoZSB0aHJlZSBpbWFnZXMsIHdlIHNlZSB0aGF0IHRoZSBsb3NzIGluIG92ZXJhbGwgZW5yb2xsbWVudCBzZWVtcyB0byBiZSBkcml2ZW4gYnkgbG9zcyBpbiB1bmRlcmdyYWR1YXRlIGVucm9sbG1lbnQsIGFuZCB0aGF0IHRoZSBncmFkdWF0ZSBlbnJvbGxtZW50IHJhbGxpZWQgc2xpZ2h0bHkgc3RhcnRpbmcgaW4gMjAxMy4gU2luY2UgZ3JhZHVhdGUgZW5yb2xsbWVudCByZXByZXNlbnRzIGFib3V0IDEwIHBlcmNlbnQgb2YgdGhlIGNvbWJpbmVkIGVucm9sbG1lbnQsIHRoZSByYWxseSBpbiBncmFkdWF0ZSBlbnJvbGxtZW50IGRvZXMgbm90IGJhbGFuY2Ugb3V0IHRoZSBkaW1pbmlzaGluZyB1bmRlcmdyYWR1YXRlIGVucm9sbG1lbnQuIApgYGB7cn0KY29tYmluZWRfZW5yb2xsbWVudF92aXoKdW5kZXJncmFkX2Vucm9sbG1lbnRfdml6CmdyYWRfZW5yb2xsbWVudF92aXoKYGBgCgpUaGUgb3ZlcmFsbCB0cmVuZCBpczogdW5kZXJncmFkdWF0ZSBlbnJvbGxtZW50IGlzIGdvaW5nIGRvd24uIAoKClRoZSBFRkZZIHN1cnZleSBkYXRhIGlzIHNwbGl0IHVwIGJ5IGdlbmRlciwgc28gd2UgY2FuIHRyeSB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBpbnRlcmVzdGluZyB0cmVuZHMgYnkgZ2VuZGVyLiAKCmBgYHtyfQplbnJvbGxtZW50X2NvbWJpbmVkX21lbiA8LSB0b3RhbF9lbnJvbGxtZW50X2NvbWJpbmVkICU+JQogIHNlbGVjdChtZW5fZW5yb2xsbWVudF8yMDEwLG1lbl9lbnJvbGxtZW50XzIwMTEsbWVuX2Vucm9sbG1lbnRfMjAxMiwKICAgICAgICAgbWVuX2Vucm9sbG1lbnRfMjAxMyxtZW5fZW5yb2xsbWVudF8yMDE0LG1lbl9lbnJvbGxtZW50XzIwMTUsCiAgICAgICAgIG1lbl9lbnJvbGxtZW50XzIwMTYsbWVuX2Vucm9sbG1lbnRfMjAxNyxtZW5fZW5yb2xsbWVudF8yMDE4KQpjb21iaW5lZF9tZW5fZW5yb2xsbWVudF9maWdzIDwtIGNvbFN1bXMoZW5yb2xsbWVudF9jb21iaW5lZF9tZW4pCnllYXJzIDwtIGMoMjAxMCwyMDExLDIwMTIsMjAxMywyMDE0LDIwMTUsMjAxNiwyMDE3LDIwMTgpCmNvbWJpbmVkX2Vucm9sbG1lbnRfbWVuX2ZpZ3NfZGYgPC0gZGF0YS5mcmFtZSh5ZWFycyxjb21iaW5lZF9tZW5fZW5yb2xsbWVudF9maWdzKQpjb21iaW5lZF9lbnJvbGxtZW50X21lbl92aXogPC0gZ2dwbG90KGRhdGE9Y29tYmluZWRfZW5yb2xsbWVudF9tZW5fZmlnc19kZixhZXMoeD15ZWFycyx5PWNvbWJpbmVkX21lbl9lbnJvbGxtZW50X2ZpZ3MpKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9saW5lKCkgKwogIGxhYnModGl0bGU9IkNvbWJpbmVkIE1lbnMgRW5yb2xsbWVudCAoMjAxMC0yMDE4KSIseD0iWWVhciIseT0iQ29tYmluZWQgbWVucyBlbnJvbGxtZW50IChhbGwgaW5zdGl0dXRpb25zKSIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCkpCmdnc2F2ZShmaWxlbmFtZT0iY29tYmluZWRfbWVuX2Vucm9sbG1lbnRfdml6LnBuZyIscGxvdD1jb21iaW5lZF9lbnJvbGxtZW50X21lbl92aXosZHBpPTgwMCkKY29tYmluZWRfZW5yb2xsbWVudF9tZW5fdml6CmBgYAoKTm90IGV4YWN0bHkgYSBzaG9jazogdGhlIGNvbWJpbmVkIG1lbnMgZW5yb2xsbWVudCBpcyBnb2luZyBkb3duIHF1aXRlIHNoYXJwbHkgKGEgYml0IG1vcmUgc2hhcnBseSB0aGFuIHRoZSBtZW5zK3dvbWVucyBjb21iaW5lZCBlbnJvbGxtZW50KS4gCgpgYGB7cn0KZW5yb2xsbWVudF9jb21iaW5lZF93b21lbiA8LSB0b3RhbF9lbnJvbGxtZW50X2NvbWJpbmVkICU+JQogIHNlbGVjdCh3b21lbl9lbnJvbGxtZW50XzIwMTAsd29tZW5fZW5yb2xsbWVudF8yMDExLHdvbWVuX2Vucm9sbG1lbnRfMjAxMiwKICAgICAgICAgd29tZW5fZW5yb2xsbWVudF8yMDEzLHdvbWVuX2Vucm9sbG1lbnRfMjAxNCx3b21lbl9lbnJvbGxtZW50XzIwMTUsCiAgICAgICAgIHdvbWVuX2Vucm9sbG1lbnRfMjAxNix3b21lbl9lbnJvbGxtZW50XzIwMTcsd29tZW5fZW5yb2xsbWVudF8yMDE4KQpjb21iaW5lZF93b21lbl9lbnJvbGxtZW50X2ZpZ3MgPC0gY29sU3VtcyhlbnJvbGxtZW50X2NvbWJpbmVkX3dvbWVuKQp5ZWFycyA8LSBjKDIwMTAsMjAxMSwyMDEyLDIwMTMsMjAxNCwyMDE1LDIwMTYsMjAxNywyMDE4KQpjb21iaW5lZF9lbnJvbGxtZW50X3dvbWVuX2ZpZ3NfZGYgPC0gZGF0YS5mcmFtZSh5ZWFycyxjb21iaW5lZF93b21lbl9lbnJvbGxtZW50X2ZpZ3MpCmNvbWJpbmVkX2Vucm9sbG1lbnRfd29tZW5fdml6IDwtIGdncGxvdChkYXRhPWNvbWJpbmVkX2Vucm9sbG1lbnRfd29tZW5fZmlnc19kZixhZXMoeD15ZWFycyx5PWNvbWJpbmVkX3dvbWVuX2Vucm9sbG1lbnRfZmlncykpKwogIGdlb21fcG9pbnQoKSsKICBnZW9tX2xpbmUoKSArCiAgbGFicyh0aXRsZT0iQ29tYmluZWQgV29tZW5zIEVucm9sbG1lbnQgKDIwMTAtMjAxOCkiLHg9IlllYXIiLHk9IkNvbWJpbmVkIHdvbWVucyBlbnJvbGxtZW50IChhbGwgaW5zdGl0dXRpb25zKSIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCkpCmdnc2F2ZShmaWxlbmFtZT0iY29tYmluZWRfd29tZW5fZW5yb2xsbWVudF92aXoucG5nIixwbG90PWNvbWJpbmVkX2Vucm9sbG1lbnRfd29tZW5fdml6LGRwaT04MDApCmNvbWJpbmVkX2Vucm9sbG1lbnRfd29tZW5fdml6CmBgYApUaGlzIGdpdmVzIHVzIGEgY2x1ZSB0aGF0IGEgc2xpZ2h0IHJlY292ZXJ5IGluIGVucm9sbG1lbnQgaGFzIGJlZW4gZHJpdmVuIGJ5IHdvbWVuIHJldHVybmluZyB0byBjb2xsZWdlIGJlZm9yZSBtZW4uIAoKCgoKTm93IHdlIHdhbnQgdGhpcyB0byBpbmNsdWRlIHNvbWUgb3RoZXIgZGF0YTogZ2VvZ3JhcGhpY2FsIHJlZ2lvbiAodGhpcyBpcyBmcm9tIHRoZSBIRCBkYXRhIHNldCkgYW5kIGRpc2NwbGluZXMuIApgYGB7cn0KcmVnaW9ucyA9IHNlbGVjdChkZl9oZF8xNCwidW5pdmVyc2l0eV9pZCI9VU5JVElELCJyZWdpb25fY29kZSI9T0JFUkVHKQpoZWFkKHJlZ2lvbnMpCmBgYApUaGUgdW5pdmVyc2l0eV9pZCdzIGFyZSBmYW1pbGlhciB0byB1cywgYW5kIHRoZSByZWdpb25fY29kZSdzIGFyZSBhcyBmb2xsb3dzICh0aGlzIGNhbiBiZSBmb3VuZCBvbiB0aGUgRXhjZWwgZmlsZXMgaW5jbHVkZWQgaW4gdGhlIGRhdGEgZG93bmxvYWQpLiAKCjAgLSBVUyBTZXJ2aWNlIHNjaG9vbHMgKHN1Y2ggYXMgV2VzdCBQb2ludCBvciB0aGUgTWVyY2hhbnQgTWFyaW5lIEFjYWRlbXkpCjEgLSBOZXcgRW5nbGFuZCAoQ1QgTUUgTUEgTkggUkkgVlQpCjIgLSBNaWQgRWFzdCAoREUgREMgTUQgTkogTlkgUEEpCjMgLSBHcmVhdCBMYWtlcyAoSUwgSU4gTUkgT0ggV0kpCjQgLSBQbGFpbnMgKElBIEtTIE1OIE1PIE5FIE5EIFNEKQo1IC0gU291dGhlYXN0IChBTCBBUiBGTCBHQSBLWSBMQSBNUyBOQyBTQyBUTiBWQSBXVikKNiAtIFNvdXRod2VzdCAoQVogTk0gT0sgVFgpCjcgLSBSb2NreSBNb3VudGFpbnMgKENPIElEIE1UIFVUIFdZKQo4IC0gRmFyIFdlc3QgKEFLIENBIEhJIE5WIE9SIFdBKQo5IC0gT3V0bHlpbmcgYXJlYXMgKEFTIEZNIEdVIE1IIE1QIFBSIFBXIFZJKQotMyAtIE5vdCBhdmFpbGFibGUKClNpZGVub3RlOiBpdCdzIGRlYmF0YWJsZSBpZiB0aGVzZSByZWdpb25zIGFyZSBpZGVhbC4gTmV3IEVuZ2xhbmQgaXMgd2VsbC1kZWZpbmVkIGFuZCBjb252ZW50aW9uYWwsIGJ1dCwgZS5nLiwgYnVuZGxpbmcgTWlubmVzb3RhIHdpdGggS2Fuc2FzIGluc3RlYWQgb2Ygd2l0aCBXaXNjb25zaW4gaXMgbWF5YmUgZGViYXRhYmxlLiAgCgpOb3cgd2Ugd2FudCB0byBjb21iaW5lIHRoZSByZWdpb25zIGRhdGEgd2l0aCB0aGUgZW5yb2xsbWVudCBkYXRhLiAKVGhlIGZvbGxvd2luZyBjb2RlIGNvbWJpbmVzIHRoZSBlbnJvbGxtZW50IGRhdGEgZnJvbSB0aGUgRUZGWSBzdXJ2ZXkgd2l0aCB0aGUgcmVnaW9uIGNvZGVzIHNvIHRoYXQgd2UgY2FuIGFuYWx5emUgcmVnaW9uYWwgdHJlbmRzLiAKCmBgYHtyfQp0b3RhbF9lbnJvbGxtZW50X3JlZ2lvbnMgPC0gdG90YWxfZW5yb2xsbWVudCAlPiUKICBpbm5lcl9qb2luKHJlZ2lvbnMsIGJ5ID0gInVuaXZlcnNpdHlfaWQiKQpjb21iaW5lZF9lbnJvbGxtZW50X3JlZ2lvbnMgPC0gdG90YWxfZW5yb2xsbWVudF9yZWdpb25zICU+JQogIGZpbHRlcihsZXZlbF9zdHVkeT09OTk5KQoKCmNvbWJpbmVkX25vX2dlbmRlcl9lbnJvbGxtZW50X3JlZ2lvbnMgPC0gY29tYmluZWRfZW5yb2xsbWVudF9yZWdpb25zICU+JQogIHNlbGVjdChyZWdpb25fY29kZSx0b3RhbF9lbnJvbGxtZW50XzIwMTAsdG90YWxfZW5yb2xsbWVudF8yMDExLHRvdGFsX2Vucm9sbG1lbnRfMjAxMiwKICAgICAgICAgdG90YWxfZW5yb2xsbWVudF8yMDEzLHRvdGFsX2Vucm9sbG1lbnRfMjAxNCx0b3RhbF9lbnJvbGxtZW50XzIwMTUsCiAgICAgICAgIHRvdGFsX2Vucm9sbG1lbnRfMjAxNix0b3RhbF9lbnJvbGxtZW50XzIwMTcsdG90YWxfZW5yb2xsbWVudF8yMDE4KQpgYGAKCldlIGhhdmVuJ3QgdG90YWxseSB0aWRpZWQgdGhlIGRhdGEsIGJ1dCBpdCdzIGdvb2QgZW5vdWdoIGZvciBvdXIgcHVycG9zZXMgKHRoZSBpZGVhbCB0aWRpZWQgZGF0YSB0YWJsZSBjb250YWlucyBhIHNpbmdsZSBtZWFzdXJlbWVudCBpbiBlYWNoIHJvdywgc28gd2Ugd291bGRuJ3QgaGF2ZSBlbnJvbGxtZW50IGZpZ3VyZXMgZnJvbSBtdWx0aXBsZSB5ZWFycyBpbiB0aGUgc2FtZSByb3cpLiBUaGUgZm9sbG93aW5nIGNodW5rIHNlcnZlcyB0byBmaW5kIHRoZSB0b3RhbCBlbnJvbGxtZW50IHBlciByZWdpb24gYXMgYSBmdW5jdGlvbiBvZiB0aGUgeWVhci4gCgpgYGB7cn0KY29tYmluZWRfbm9fZ2VuZGVyX2Vucm9sbG1lbnRfcmVnaW9uc19ieV9yZWdpb25zIDwtIGNvbWJpbmVkX25vX2dlbmRlcl9lbnJvbGxtZW50X3JlZ2lvbnMgJT4lCiAgZ3JvdXBfYnkocmVnaW9uX2NvZGUpJT4lCiAgdHJhbnNtdXRlKCIyMDEwIj1zdW0odG90YWxfZW5yb2xsbWVudF8yMDEwKSwKICAgICAgICAgIjIwMTEiPXN1bSh0b3RhbF9lbnJvbGxtZW50XzIwMTEpLAogICAgICAgICAiMjAxMiI9c3VtKHRvdGFsX2Vucm9sbG1lbnRfMjAxMiksCiAgICAgICAgICIyMDEzIj1zdW0odG90YWxfZW5yb2xsbWVudF8yMDEzKSwKICAgICAgICAgIjIwMTQiPXN1bSh0b3RhbF9lbnJvbGxtZW50XzIwMTQpLAogICAgICAgICAiMjAxNSI9c3VtKHRvdGFsX2Vucm9sbG1lbnRfMjAxNSksCiAgICAgICAgICIyMDE2Ij1zdW0odG90YWxfZW5yb2xsbWVudF8yMDE2KSwKICAgICAgICAgIjIwMTciPXN1bSh0b3RhbF9lbnJvbGxtZW50XzIwMTcpLAogICAgICAgICAiMjAxOCI9c3VtKHRvdGFsX2Vucm9sbG1lbnRfMjAxOCkpICU+JQogIGRpc3RpbmN0KHJlZ2lvbl9jb2RlLC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIGFycmFuZ2UocmVnaW9uX2NvZGUpCnJlc2hhcGVkX3JlZ2lvbnNfbm9fZ2VuZGVyIDwtIGNvbWJpbmVkX25vX2dlbmRlcl9lbnJvbGxtZW50X3JlZ2lvbnNfYnlfcmVnaW9ucyAlPiUKICBnYXRoZXIoJzIwMTAnLCcyMDExJywnMjAxMicsJzIwMTMnLCcyMDE0JywnMjAxNScsJzIwMTYnLCcyMDE3JywnMjAxOCcsa2V5PSd5ZWFyJyx2YWx1ZT0nZW5yb2xsbWVudCcpCmBgYAoKCkxldHMgdGFrZSBhIGxvb2sgYXQgdGhpcyBuZXcgdGFibGU6IGl0IGNvbnRhaW5zIG9ubHkgdGhyZWUgdmFyaWFibGVzIChyZWdpb25fY29kZSwgeWVhciwgYW5kIGVucm9sbG1lbnQpLCBhbmQgZWFjaCByb3cgcmVwcmVzZW50cyBhIHNpbmdsZSBlbnJvbGxtZW50IGZpZ3VyZS4gIAoKYGBge3J9CmhlYWQocmVzaGFwZWRfcmVnaW9uc19ub19nZW5kZXIpCmBgYAoKTGV0J3MgZ2V0IGludG8gdGhlIHBsb3R0aW5nIG9mIHRoaXMgZGF0YS4gRmlyc3Qgd2UgbWFrZSBhIGRhdGFmcmFtZS4gCmBgYHtyfQpyZXNoYXBlZF9yZWdpb25zX25vX2dlbmRlcl9kZiA8LSBkYXRhLmZyYW1lKHJlc2hhcGVkX3JlZ2lvbnNfbm9fZ2VuZGVyKQpyZXNoYXBlZF9yZWdpb25zX25vX2dlbmRlcl9kZgpgYGAKVGhlIHJlZ2lvbiBjb2RlIDAgc3RhbmRzIGZvciBVUyBTZXJ2aWNlIEFjYWRlbWllcyBsaWtlIFdlc3QgUG9pbnQsIHdoZXJlIGVucm9sbG1lbnQgaXMgYmFzaWNhbGx5IGNvbnN0YW50IChhcyB3ZWxsIGFzIGJlaW5nIGEgdGlueSBmcmFjdGlvbiBvZiBhbnkgb2YgdGhlIG90aGVyIHJlZ2lvbnMpLiBUaGUgc2FtZSB0aGluZyBpcyB0cnVlIGZvciB0aGUgb3V0bHlpbmcgYXJlYXMgc3VjaCBhcyBHdWFtLiBTbyB3ZSBtaWdodCBhcyB3ZWxsIGZpbHRlciBpdCBvdXQgCgpgYGB7cn0KcmVnaW9uc19ub19nZW5kZXJfZGYgPC0gCiAgcmVzaGFwZWRfcmVnaW9uc19ub19nZW5kZXJfZGYgJT4lCiAgZmlsdGVyKHJlZ2lvbl9jb2RlICE9IDAgJiByZWdpb25fY29kZSAhPSA5KQpoZWFkKHJlZ2lvbnNfbm9fZ2VuZGVyX2RmKQpgYGAKCgoKCk5vdyB3ZSBjYW4gY3JlYXRlIG91ciBsaXN0IG9mIG9iamVjdHMuIAoKCmBgYHtyfQpsYWJlbHNfcmVnaW9ucyA8LSAgYygnTmV3IEVuZ2xhbmQgKENUIE1FIE1BIE5IIFJJIFZUKScsIAogICAgICAgICAgICAgICAgICAgICAnTWlkIEVhc3QgKERFIERDIE1EIE5KIE5ZIFBBKScsCiAgICAgICAgICAgICAgICAgICAgICdHcmVhdCBMYWtlcyAoSUwgSU4gTUkgT0ggV0kpJywgCiAgICAgICAgICAgICAgICAgICAgICdQbGFpbnMgKElBIEtTIE1OIE1PIE5FIE5EIFNEKScsICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAnU291dGhlYXN0IChBTCBBUiBGTCBHQSBLWSBMQSBNUyBOQyBTQyBUTiBWQSBXViknLAogICAgICAgICAgICAgICAgICAgICAnU291dGh3ZXN0IChBWiBOTSBPSyBUWCknLAogICAgICAgICAgICAgICAgICAgICAnUm9ja3kgTW91bnRhaW5zIChDTyBJRCBNVCBVVCBXWSknLCAKICAgICAgICAgICAgICAgICAgICAgJ0ZhciBXZXN0IChBSyBDQSBISSBOViBPUiBXQSknKQpgYGAKTGV0J3MgYnVpbGQgYSBtb3JlIGNvbmNpc2Ugc2V0IG9mIGxhYmVsczogCmBgYHtyfQpsYWJlbHNfcmVnaW9uc19jb25jaXNlIDwtIGMoJ05ldyBFbmdsYW5kJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTWlkIEVhc3QnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0dyZWF0IExha2VzJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUGxhaW5zJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnU291dGhlYXN0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdTb3V0aHdlc3QnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1JvY2t5IE1vdW50YWlucycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZhciBXZXN0JykKYGBgCgoKVGhlIGZvbGxvd2luZyBpcyBhIGdncGxvdCB2aXN1YWxpemF0aW9uLiAKYGBge3J9CnJlZ2lvbnNfdml6IDwtIGdncGxvdChkYXRhPXJlZ2lvbnNfbm9fZ2VuZGVyX2RmLGFlcyh4PXN0cnRvaSh5ZWFyKS0yMDAwLHk9ZW5yb2xsbWVudC8xMF42LGNvbG9yPWZhY3RvcihyZWdpb25fY29kZSksZ3JvdXA9ZmFjdG9yKHJlZ2lvbl9jb2RlKSxsYWJlbD1mYWN0b3IocmVnaW9uX2NvZGUpKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fbGluZShrZXlfZ2x5cGg9InJlY3QiKSsKICB0aGVtZV9saWdodCgpKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvcj0iY2hvY29sYXRlIixzaXplPTE2LGZhY2U9ImJvbGQiKSwKICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplPTAuNSwgbGluZXR5cGU9InNvbGlkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSJkYXJrYmx1ZSIpLAogICAgICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIsIGNvbG91ciA9ICJibGFjayIpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKSsKICBzY2FsZV9jb2xvcl9odWUobmFtZT0iUmVnaW9uIiwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9bGFiZWxzX3JlZ2lvbnNfY29uY2lzZSkrCiAgbGFicyh5PSJFbnJvbGxtZW50IChpbiBtaWxsaW9ucykiLHg9IlllYXIiLHRpdGxlPSJSZWdpb25hbCBlbnJvbGxtZW50IHRyZW5kczogMjAxMC0yMDE4IikKZ2dzYXZlKGZpbGVuYW1lPSJyZWdpb25hbF90cmVuZHMucG5nIixwbG90PXJlZ2lvbnNfdml6LGRwaT04MDApCnJlZ2lvbnNfdml6CmBgYAoKVGhhdCBnaXZlcyB1cyBhIHBpY3R1cmUgb2Ygc29tZSBvZiB0aGUgdHJlbmRzLiAKCk5ldyBFbmdsYW5kIGlzIHN0YXRpYy4KCkZhciBXZXN0LCBhZnRlciBhIGRpcCBpbiAyMDEzLCBzZWVtcyB0byBiZSByZWNvdmVyaW5nLiAKClJvY2t5IE1vdW50YWlucyBpcyBpbmNyZWFzaW5nIGF0IGEgbW9kZXN0IGJ1dCBzdGVhZHkgcmF0ZS4KClRoZSBHcmVhdCBMYWtlcyByZWdpb24gaXMgZGVjcmVhc2luZy4gCgoKCj09PT09PT1ESVNDSVBMSU5BUlkgVFJFTkRTPT09PT09PT0KCk9rYXksIG5leHQgd2Ugd291bGQgbGlrZSB0byBnZXQgc29tZSBkaXNjaXBsaW5hcnkgaW5mb3JtYXRpb24gYWRkZWQgaW4uIFJlbWVtYmVyIHRoYXQgdGhlc2UgYXJlIHN0b3JlZCBpbiB0aGUgY29tcGxldGlvbnMgdGFibGVzOiAKYGBge3J9CmhlYWQoZGZfY29tcGxldGlvbnNfMTQpCmBgYApUaGUgaW1wb3J0YW50IGNvbHVtbnMgaGVyZSBhcmUgVU5JVElEICh1bml2ZXJzaXR5IElEIGNvZGUpLCBDSVBDT0RFIChkaXNjcGxpbmFyeSBzdWJqZWN0KSwgQ1RPVEFMVCAodG90YWwgY29tcGxldGlvbnMgaW4gdGhlIGRpc2NpcGxpbmUpLiAKCldlIGhhdmUgYSBsaXN0IG9mIENJUENPREVzIG9mIHRoZSBmb3JtIHh4Lnh4eHguIFRoZSBmaXJzdCB0d28gZGlnaXRzIHN0b3JlIHRoZSBicm9hZCByZWdpb24gKHN1Y2ggYXMgQmlvbG9neSBvciBFZHVjYXRpb24pIGFuZCB0aGUgbGFzdCBmb3VyIGluZGljYXRlIHRoZSBzcGVjaWFsdHkgKHN1Y2ggYXMgQmlvY2hlbWlzdHJ5IG9yIFdpbGRsaWZlIEJpb2xvZ3kpLiAKCkF0IGZpcnN0LCB3ZSB3YW50IHRvIHN0dWR5IGJyb2FkIHRyZW5kcywgc28gd2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiB0aGUgdHJlbmRzIGF0IHRoZSB4eC4tbGV2ZWwuIFdlIG5lZWQgdG8gc3BsaXQgdXAgdGhlIENJUCBjb2RlIGJ5IHRoZSAnLicgZGVsaW1pdGVyIGFuZCB0aGVuIGdyYWIgdGhlIGZpcnN0IHBhcnQuIAoKCgoKYGBge3J9CmNvbXBsZXRpb25zX3NwbGl0XzEwIDwtIGRmX2NvbXBsZXRpb25zXzEwICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpLAogICAgICAgICBzdWJjaXAgPSBzdWJzdHIoQ0lQQ09ERSwyLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfMTAgPC0gY29tcGxldGlvbnNfc3BsaXRfMTAgJT4lCiAgc2VsZWN0KFVOSVRJRCxjb2Fyc2VjaXAsQ1RPVEFMVCkKYGBgCkxldHMgc2VlIGlmIHRoYXQgd29ya2VkOiAKYGBge3J9CmhlYWQoY29tcGxldGlvbnNfY29hcnNlXzEwKQpgYGAKTm90IHRvbyBiYWQhIEJ1dCB3ZSBvbmx5IHdhbnQgdGhlIHRvdGFscyBncm91cGVkIGJ5IENJUCAtLSB3ZSBkb24ndCByZWFsbHkgY2FyZSBhYm91dCB0aGUgVU5JVElEOiAKYGBge3J9CmNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTAgPC0gY29tcGxldGlvbnNfY29hcnNlXzEwICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDEwIiA9IHN1bShDVE9UQUxUKSkKICBjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzEwCmBgYApUaGF0J3Mgd2hhdCB3ZSB3YW50ZWQ6IHRoZSB0b3RhbCBudW1iZXIgb2YgY29tcGxldGVkIGRlZ3JlZXMgYnkgQ0lQQ29kZS4gSW4gdGhlIGNodW5rIGJlbG93IHdlIGhhdmUgcmVwZWF0ZWQgdGhpcyBvcGVyYXRpb24gZm9yIGVhY2ggb2YgdGhlIG90aGVyIHllYXJzIGluIG91ciBkYXRhc2V0LiAKCmBgYHtyfQpjb21wbGV0aW9uc19zcGxpdF8xMSA8LSBkZl9jb21wbGV0aW9uc18xMSAlPiUKICBtdXRhdGUoY29hcnNlY2lwID0gc3Vic3RyKENJUENPREUsMSwyKSkKY29tcGxldGlvbnNfY29hcnNlX2J5X2NpcF8xMSA8LSBjb21wbGV0aW9uc19zcGxpdF8xMSAlPiUKICBzZWxlY3QoVU5JVElELGNvYXJzZWNpcCxDVE9UQUxUKSAlPiUKICBncm91cF9ieShjb2Fyc2VjaXApICU+JQogIHN1bW1hcml6ZSgiMjAxMSIgPSBzdW0oQ1RPVEFMVCkpCiAgCmNvbXBsZXRpb25zX3NwbGl0XzEyIDwtIGRmX2NvbXBsZXRpb25zXzEyICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzEyIDwtIGNvbXBsZXRpb25zX3NwbGl0XzEyICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDEyIiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzEzIDwtIGRmX2NvbXBsZXRpb25zXzEzICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzEzIDwtIGNvbXBsZXRpb25zX3NwbGl0XzEzICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDEzIiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzE0IDwtIGRmX2NvbXBsZXRpb25zXzE0ICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzE0IDwtIGNvbXBsZXRpb25zX3NwbGl0XzE0ICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDE0IiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzE1IDwtIGRmX2NvbXBsZXRpb25zXzE1ICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzE1IDwtIGNvbXBsZXRpb25zX3NwbGl0XzE1ICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDE1IiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzE2IDwtIGRmX2NvbXBsZXRpb25zXzE2ICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzE2IDwtIGNvbXBsZXRpb25zX3NwbGl0XzE2ICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDE2IiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzE3IDwtIGRmX2NvbXBsZXRpb25zXzE3ICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzE3IDwtIGNvbXBsZXRpb25zX3NwbGl0XzE3ICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDE3IiA9IHN1bShDVE9UQUxUKSkKCmNvbXBsZXRpb25zX3NwbGl0XzE4IDwtIGRmX2NvbXBsZXRpb25zXzE4ICU+JQogIG11dGF0ZShjb2Fyc2VjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKQpjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzE4IDwtIGNvbXBsZXRpb25zX3NwbGl0XzE4ICU+JQogIHNlbGVjdChVTklUSUQsY29hcnNlY2lwLENUT1RBTFQpICU+JQogIGdyb3VwX2J5KGNvYXJzZWNpcCkgJT4lCiAgc3VtbWFyaXplKCIyMDE4IiA9IHN1bShDVE9UQUxUKSkKYGBgCgpXZSBub3cgaGF2ZSByZWFzb25hYmx5IG9yZ2FuaXplZCBjb21wbGV0aW9uIGRhdGEgZm9yIGFsbCB0aGUgcHJvZ3JhbXMuIFRoZSBuZXh0IHN0ZXAgaXMgdG8gY29tYmluZSBpdCBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUuCgpXZSB3YW50IHRoZSBjb2Fyc2VfY2lwIGNvbXBsZXRpb24gZGF0YSB0byBiZSBvcmdhbml6ZWQgaW50byBhIHNpbmdsZSB0YWJsZS4gCgpgYGB7cn0KYWxsX2NvbXBsZXRpb25zX2NpcCA8LSBjb21wbGV0aW9uc19jb2Fyc2VfYnlfY2lwXzEwICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTEpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTIpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTMpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTQpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTUpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTYpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTcpICU+JQogIG1lcmdlKGNvbXBsZXRpb25zX2NvYXJzZV9ieV9jaXBfMTgpCmFsbF9jb21wbGV0aW9uc19jaXAgCmBgYAoKVGhpcyBpcyBuaWNlLCBidXQgd2UnZCByZWFsbHkgbGlrZSB0byBoYXZlIHRoZSB5ZWFycyBhcyB2YXJpYWJsZXMsIGluIG9yZGVyIHRvIGNvbXBseSB3aXRoIHRoZSB0aWR5IGRhdGEgcGFyYWRpZ20uIChXZSBjb3VsZCBoYXZlIGRvbmUgdGhpcyBlYXJsaWVyLikKCmBgYHtyfQpyZXNoYXBlZF9jb21wbGV0aW9ucyA8LSBhbGxfY29tcGxldGlvbnNfY2lwICU+JQogIGdhdGhlcigiMjAxMCIsIjIwMTEiLCIyMDEyIiwiMjAxMyIsIjIwMTQiLCIyMDE1IiwiMjAxNiIsIjIwMTciLCIyMDE4IixrZXk9InllYXIiLHZhbHVlPSJjb21wbGV0aW9ucyIpCnJlc2hhcGVkX2NvbXBsZXRpb25zCmBgYAoKVGhpcyBpcyBwcm9wZXJseSB0aWRpZWQgY29hcnNlX2NpcCBjb21wbGV0aW9uIGRhdGEgYnkgeWVhci4gCgpUaGVyZSBpcyBvbmUgdGhhdCB3ZSB3b3VsZCBsaWtlIHRvIGV4Y2x1ZGU6IENpcCBjb2RlIDk5LCB3aGljaCBkZXNjcmliZXMgdGhlIGdyYW5kIHRvdGFsIG9mIGFsbCBkZWdyZWUgY29tcGxldGlvbnMuCgpgYGB7cn0KcmVzaGFwZWRfY29tcGxldGlvbnMgPC0gcmVzaGFwZWRfY29tcGxldGlvbnMgJT4lCiAgZmlsdGVyKGNvYXJzZWNpcCAhPSAnOTknKQpgYGAKCgpOb3cgd2UgY2FuIGRvIGEgYmlnIGZhY2V0IGdyYXBoLCBwbG90dGluZyBmb3IgZWFjaCBDSVBDT0RFIGEgZGlmZmVyZW50IGxpbmUgZ3JhcGggc2hvd2luZyBjb21wbGV0aW9ucyBvZiBkZWdyZWVzIGluIHRoYXQgQ0lQQ09ERSBvdmVyIHRoZSB5ZWFycyAyMDEwLTIwMTguIAoKV0FSTklORzogdGhlIGZpcnN0IHZlcnNpb24gaXMgZ29pbmcgdG8gbG9vayB2ZXJ5IHJvdWdoIGluIFJTdHVkaW8uIEhvd2V2ZXIgdGhlIHZlcnNpb24gdGhhdCBzYXZlcyB0byB0aGUgZGlyZWN0b3J5IGlzIG5vdCBhcyBjbHV0dGVyZWQuIAoKYGBge3J9CmJhZF9mYWNldHMgPC0gZ2dwbG90KGRhdGEgPSByZXNoYXBlZF9jb21wbGV0aW9ucywgYWVzKHllYXIsIGNvbXBsZXRpb25zLGdyb3VwPTEpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gInN0ZWVsYmx1ZSIsIHNpemUgPSAxKSArCiAgZ2VvbV9wb2ludChjb2xvcj0ic3RlZWxibHVlIikgKyAKICBsYWJzKHRpdGxlID0gIkRlZ3JlZSBjb21wbGV0aW9ucyBieSBDSVAgY29kZSAoMjAxMC0yMDE4KSIsCiAgICAgICB5ID0gIkNvdW50IG9mIGRlZ3JlZSBjb21wbGV0aW9ucyIsIHggPSAieWVhciIpICsgCiAgZmFjZXRfd3JhcCh+Y29hcnNlY2lwKQoKZ2dzYXZlKGZpbGVuYW1lPSdiYWRfZmFjZXRzLnBuZycscGxvdD1iYWRfZmFjZXRzLGRwaT04MDApCmJhZF9mYWNldHMKYGBgCgpUaGlzIGdpdmVzIHVzIHNvbWUgZ29vZCB0cmVuZHMgKDA5LCAxMSwgMTQsIDI0LCA1MSwgNTIpIGFsbCBzZWVtIHRvIGJlIGluY3JlYXNpbmcuIFRoZXJlIGlzIG9uZSB0aGluZyB3ZSBjYW4gZG8gdG8gbWFrZSBpdCBhIGJpdCBtb3JlIG1lYW5pbmdmdWw6IHN3YXAgb3V0IGNpcCBjb2RlcyBmb3IgbmFtZXMgKHdoaWNoIEkndmUgc3VtbWFyaXplZCBiZWNhdXNlIHRoZSBDSVBjb2RlIGRlc2NyaXB0aW9ucyBhcmUgc29tZXRpbWVzIGxlbmd0aHkpLiAKCmBgYHtyfQpjb2Rlc19udW1iZXIgPC0gYygxLDMsNCw1LDksMTAsMTEsMTIsMTMsMTQsMTUsMTYsMTksMjIsMjMsMjQsMjUsMjYsMjcsIDI5LDMwLDMxLDM4LDM5LDQwLDQxLDQyLDQzLDQ0LDQ1LDQ2LDQ3LDQ4LDQ5LDUwLDUxLDUyLDU0KQpjb2Rlc19jaHIgPC0gYXMuY2hhcmFjdGVyKGNvZGVzX251bWJlcikKY2lwX2RhdGEgPC0gZGF0YS5mcmFtZSgiY29hcnNlY2lwIiA9IGNvZGVzX2NociwgImFyZWEiID0gYygiYWdyaSIsImVudnNjaSIsImFyY2gvdXJiIiwiYXJlYSBzdHVkaWVzIiwiY29tbXMvam91cm4iLCJncmFwaCBkZXMuIiwiY29tcC4iLCJiZWF1dHkiLCJlZHVjIiwiZW5nbnMiLCJ0ZWNobmljaWFuIiwibGFuZ3MiLCJodW1hbiBzY2kiLCJsYXciLCJsaXQiLCJsaWIgYXJ0cyIsImxpYnJhcnkiLCJiaW8iLCJtYXRoIiwiaW50ZWwiLCJnZW4gc3R1ZGllcyIsIm1nbXQiLCJwaGlsIiwicmVsIiwiY2hlbSIsInNjaSB0ZWNoIiwicHN5Y2giLCJyZXNwb25kZXIiLCJhZG1pbiIsInNvYyBzY2kiLCJ0cmFkZXMiLCJhc3NvcnRlZCB0ZWNoIiwicHJvZCB0cmFkZSIsImFpciB0cmFmZiIsImFydCIsImhlYWx0aCIsImJ1cy9maW4iLCJoaXN0b3J5IikpCmNpcF9kYXRhCmBgYAoKTGV0J3MgZ2V0IHRoZSBhcmVhIG5hbWVzIGluIHRoZXJlCmBgYHtyfQpyZXNoYXBlZF9jb21wbGV0aW9ucwpyZXNoYXBlZF9jb21wbGV0aW9uc19hcmVhcyA8LSByZXNoYXBlZF9jb21wbGV0aW9ucyAlPiUKICBtZXJnZShjaXBfZGF0YSkgJT4lCiAgYXJyYW5nZShkZXNjKGNvYXJzZWNpcCkpCnJlc2hhcGVkX2NvbXBsZXRpb25zX2FyZWFzCmBgYApOb3cgbGV0cyBkbyB0aGUgc2FtZSBmYWNldCB3cmFwOiAKCmBgYHtyfQpkaXNjaXBsaW5lX3ZpeiA8LSBnZ3Bsb3QoZGF0YSA9IHJlc2hhcGVkX2NvbXBsZXRpb25zX2FyZWFzLCBhZXMoeWVhciwgY29tcGxldGlvbnMsZ3JvdXA9MSkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAic3RlZWxibHVlIiwgc2l6ZSA9IDEpICsKICBnZW9tX3BvaW50KGNvbG9yPSJzdGVlbGJsdWUiKSArIAogIGxhYnModGl0bGUgPSAiRGVncmVlIGNvbXBsZXRpb25zIGJ5IGFyZWEgKDIwMTAtMjAxOCkiLAogICAgICAgeSA9ICJDb3VudCBvZiBkZWdyZWUgY29tcGxldGlvbnMiLCB4ID0gInllYXIiKSArCiAgCiAgZmFjZXRfd3JhcCh+YXJlYSkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKQpnZ3NhdmUoZmlsZW5hbWU9J2Rpc2NpcGxpbmVfZ3JhcGgucG5nJyxwbG90PWRpc2NpcGxpbmVfdml6LGRwaT00MDApCgpkaXNjaXBsaW5lX3ZpegpgYGAKVGhpcyBpcyBuaWNlIChpdCBpcyB0aW55IGluIFJzdHVkaW8gYnV0IEkgcmVjb21tZW5kIGxvb2tpbmcgYXQgdGhlIHNhdmVkIGltYWdlIGRpc2NpcGxpbmVfZ3JhcGgucG5nIGluIHRoZSBkaXJlY3RvcnkpLCBidXQgaXQgd2FzaGVzIG91dCBhIGxvdCBvZiB0aGUgdHJlbmRzIGZvciB0aGUgbG93ZXItZW5yb2xsbWVudCBhcmVhcy4gU28gd2UgbWlnaHQgd2FudCB0byBsb29rIGF0IHNwZWNpZmljIGRpc2NpcGxpbmVzLiAKCkxldCdzIGNyZWF0ZSBhIG5ldyBkYXRhZnJhbWUgdGhhdCB0cmFja3Mgc29tZXRoaW5nIHZlcnkgY29hcnNlOiBwZXJjZW50IGdyb3d0aCBpbiBhIGZpZWxkIG92ZXIgdGhlIHRpbWUgaW50ZXJ2YWwgMjAxMC0yMDE4LiBXZSBjYWxjdWxhdGUgdGhpcyBhcyAxMDAqKGNoYW5nZSBpbiBjb21wbGV0aW9ucykvKDIwMTAgY29tcGxldGlvbnMpLiAgCmBgYHtyfQpoZWFkKHJlc2hhcGVkX2NvbXBsZXRpb25zX2FyZWFzKQpuZXRfY29tcGxldGlvbnMgPC0gIHJlc2hhcGVkX2NvbXBsZXRpb25zX2FyZWFzICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDEwIHwgeWVhciA9PSAyMDE4KSAlPiUKICBzcHJlYWQoeWVhciwgY29tcGxldGlvbnMpIApjb2xuYW1lcyhuZXRfY29tcGxldGlvbnMpIDwtIGMoJ2NvYXJzZWNpcCcsJ2FyZWEnLCdjb21wc18yMDEwJywnY29tcHNfMjAxOCcpCm5ldF9jb21wbGV0aW9ucyA8LSBuZXRfY29tcGxldGlvbnMgJT4lCiAgbXV0YXRlKHBlcmNlbnRfY2hhbmdlID0gMTAwKihjb21wc18yMDE4L2NvbXBzXzIwMTAtMSkpICU+JQogIGFycmFuZ2UoZGVzYyhwZXJjZW50X2NoYW5nZSkpCmhlYWQobmV0X2NvbXBsZXRpb25zLDEwKQpgYGAKV2Ugc2VlIHRoYXQgdGhlIDEwIG1vc3QgaW5jcmVhc2luZyBhcmVhcyBhcmUgdGhvc2UgbGlzdGVkIGFib3ZlIFRvIGNsYXJpZnkgdGhlc2UgYWJicmV2aWF0aW9uczogc2NpIHRlY2ggY292ZXJzIGJhc2ljYWxseSBhbnkgZGVncmVlIHByZXBhcmluZyBmb3Igd29yayBhcyBhIHRlY2huaWNpYW4gaW4gYSBsYWIsIGNvbXAuIGlzIGNvbXB1dGVyIHNjaWVuY2UsIHByb2QgdHJhZGUgaXMgZGVncmVlcyByZWxhdGVkIHRvIHByb2R1Y3Rpb24gbGlrZSBtYW51ZmFjdHVyaW5nLCBtZ210IGlzIG1hbmFnZW1lbnQgc2NpZW5jZSwgZW5nbnMgaXMgZW5naW5lZXJpbmcuCgpTb21lIG9mIHRoZXNlIGFyZSBub3Qgc3VycHJpc2luZyAobm9ib2R5IGNhbiBiZSBzaG9ja2VkIGJ5IGJyb256ZSBtZWRhbCBmb3IgY29tcHV0ZXIgYXJlYXMpOyBzb21lIGFyZSBtb3JlIHN1cnByaXNpbmcgKGxpYmVyYWwgYXJ0cyBoYXMgYSBsb3QgbW9yZSBjb21wbGV0aW9ucyBkZXNwaXRlIGEgbG90IG9mIHByZXNzIGFib3V0IHRoZSBkZWF0aCBvZiBsaWJlcmFsIGFydHMgZGVncmVlcykuIAoKQW5zd2VyIHRvIG9uZSBvZiBvdXIgcXVlc3Rpb25zOiBBIGNvbXBhcmF0aXZlbHkgc21hbGwgaW52ZXN0bWVudCBpbiBlZHVjYXRpb25hbCByZXNvdXJjZXMgY291bGQgcmV0dXJuIG1ham9yIGJlbmVmaXRzOyBhbHNvIG1vcmUgbWF0aCBhbmQgY29tcHV0ZXIgc2NpZW5jZSByZXNvdXJjZXMuCgoKClRoZSB2aXN1YWxpemF0aW9uIGJlbG93IG1ha2VzIHRoaXMgYSBiaXQgbW9yZSBzdHJpa2luZy4gVGhlIHJlZCBsaW5lIHJlcHJlc2VudHMgYSAwIHBlcmNlbnQgY2hhbmdlIGluIHRoZSBudW1iZXIgb2YgZGVncmVlIGNvbXBsZXRpb25zLiAKYGBge3J9CnBlcmNlbnRzX3ZpeiA8LSBnZ3Bsb3QoZGF0YT1uZXRfY29tcGxldGlvbnMsYWVzKHg9YXJlYSx5PXBlcmNlbnRfY2hhbmdlKSkrCiAgZ2VvbV9wb2ludCgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gLTkwLHZqdXN0PTAuNDUpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGNvbG9yPSdyZWQnKSsKICBsYWJzKHRpdGxlPSJOZXQgY2hhbmdlIGluIGRlZ3JlZSBjb21wbGV0aW9uOiAyMDEwLTIwMTgiLHg9J2FyZWEnLHk9J3BlcmNlbnQgY2hhbmdlIGluIGVucm9sbG1lbnQnKQpwZXJjZW50c192aXoKYGBgCgpUaGlzIGlzIGEgYml0IGNsdXR0ZXJlZDogbGV0J3MgcGluIGRvd24gdGhlIDEwIGZhc3Rlc3QtZ3Jvd2luZyBhbmQgMTAgZmFzdGVzdC1zaHJpbmtpbmcgYXJlYXMuIAoKYGBge3J9CnRlbl9mYXN0ZXN0IDwtIG5ldF9jb21wbGV0aW9ucyAlPiUKICBhcnJhbmdlKGRlc2MocGVyY2VudF9jaGFuZ2UpKSAlPiUKICBzbGljZSgxOjEwKQojdGVuX2Zhc3Rlc3QKCnRlbl93b3JzdCA8LSBuZXRfY29tcGxldGlvbnMgJT4lCiAgYXJyYW5nZShwZXJjZW50X2NoYW5nZSkgJT4lCiAgc2xpY2UoMToxMCkKI3Rlbl93b3JzdAoKZmFzdGVzdF92aXogPC0gZ2dwbG90KGRhdGE9dGVuX2Zhc3Rlc3QsYWVzKHg9YXJlYSx5PXBlcmNlbnRfY2hhbmdlKSkrCiAgZ2VvbV9wb2ludCgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzApKSsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCxjb2xvcj0ncmVkJykrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAtOTAsdmp1c3Q9MC40NSksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgbGFicyh0aXRsZT0iRmFzdGVzdCBhcmVhcyBvZiBncm93dGg6IDIwMTAtMjAxOCIseD0nYXJlYScseT0ncGVyY2VudCAoKykgY2hhbmdlIGluIGVucm9sbG1lbnQnKSsKICBnZ3NhdmUoJ2Zhc3Rlc3Rfdml6LnBuZycsZHBpPTgwMCkKc2xvd2VzdF92aXogPC0gZ2dwbG90KGRhdGE9dGVuX3dvcnN0LGFlcyh4PWFyZWEseT1wZXJjZW50X2NoYW5nZSkpKwogIGdlb21fcG9pbnQoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwKSkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAtOTAsdmp1c3Q9MC40NSksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsY29sb3I9J3JlZCcpKwogIGxhYnModGl0bGU9IldlYWtlc3QgYXJlYXMgb2YgZ3Jvd3RoOiAyMDEwLTIwMTgiLHg9J2FyZWEnLHk9J3BlcmNlbnQgY2hhbmdlIGluIGVucm9sbG1lbnQnKSsKICBnZ3NhdmUoJ3dlYWtlc3Rfdml6LnBuZycsZHBpPTgwMCkKZmFzdGVzdF92aXoKc2xvd2VzdF92aXoKYGBgCldlIHNhdyB0aGF0IHRoZXJlIHdhcyBzb21lIGdvb2QgZ3Jvd3RoIGluIHRoZSBtYXRoZW1hdGljcyBhcmVhLiBBbnlvbmUgd2hvIGZvbGxvd3MgaGlnaGVyLWVkdWNhdGlvbmFsIG5ld3MgaW4gbWF0aGVtYXRpY3Mgd2lsbCBrbm93IHRoYXQgc3RhdGlzdGljcyBpcyBncm93aW5nIGluIHBvcHVsYXJpdHkgYXMgYSBtYWpvciBjb3Vyc2Ugb2Ygc3R1ZHkuIEhvd2V2ZXIsIHRoZSBDb21wbGV0aW9ucyBzdXJ2ZXkgZG9lcyBub3QgcHJvdmlkZSBhIHNlcGFyYXRlICJDb2Fyc2UiIENJUCBmb3Igc3RhdHM7IGl0J3Mgc3R1Y2sgd2l0aGluIHRoZSBjb21wbGV0aW9ucyBmb3IgbWF0aCBtYWpvcnMuIExldCdzIHNlZSBpZiB3ZSBjYW4gZXh0cmFjdCBzb21lIHVzZWZ1bCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY29tcGxldGlvbiBvZiBzdGF0aXN0aWNzIGRlZ3JlZXMuIAoKClJlbWVtYmVyIHdoYXQgdGhlIG5vbi1jb2Fyc2UgY29tcGxldGlvbnMgZGF0YSBsb29rZWQgbGlrZTogCgpgYGB7cn0KaGVhZChkZl9jb21wbGV0aW9uc18xMCkKYGBgCldlIHN0aWxsIHdhbnQgdG8gYnJlYWsgYXBhcnQgdGhlIENJUENPREUgYnV0IG5vdyB3ZSB3YW50IHRvIGZpbHRlciB0byB0aGUgQ29hcnNlIENJUCAyNyAtLSB0aGlzIGlzIHdoZXJlIGFsbCB0aGUgbWF0aGVtYXRpY3MgaXMuIEhlcmUgYXJlIHRoZSBmaW5lIENJUENPREVTIGZvciBNYXRoZW1hdGljcyAoSSBzdGFydGVkIGNvbnZlcnRpbmcgdGhlIGNvZGVzIHRvIGNoYXJhY3RlciBieSBoYW5kIGJlZm9yZSBJIHJlbWVtYmVyZWQgeW91IGNhbiBjYXN0IHRvIGNoYXJhY3RlciB2ZWN0b3J3aXNlKQpgYGB7cn0Kc3ViZmllbGRzX25hbWVzIDwtIGMoIk1hdGhlbWF0aWNzLCBHZW5lcmFsIiwKICAgICAgICAgICAgICAgICAgICAgIkFsZ2VicmEgYW5kIE51bWJlciBUaGVvcnkiLAogICAgICAgICAgICAgICAgICAgICAiQW5hbHlzaXMgYW5kIEZ1bmN0aW9uYWwgQW5hbHlzaXMiLAogICAgICAgICAgICAgICAgICAgICAiVG9wb2xvZ3kgYW5kIEZvdW5kYXRpb25zIiwKICAgICAgICAgICAgICAgICAgICAgIk1hdGhlbWF0aWNzLCBPdGhlciIsCiAgICAgICAgICAgICAgICAgICAgICJBcHBsaWVkIE1hdGhlbWF0aWNzIChHZW5lcmFsKSIsCiAgICAgICAgICAgICAgICAgICAgICJDb21wdXRhdGlvbmFsIE1hdGhlbWF0aWNzIiwKICAgICAgICAgICAgICAgICAgICAgIkNvbXB1dGF0aW9uYWwgYW5kIEFwcGxpZWQgTWF0aGVtYXRpY3MiLAogICAgICAgICAgICAgICAgICAgICAiRmluYW5jaWFsIE1hdGhlbWF0aWNzIiwKICAgICAgICAgICAgICAgICAgICAgIk1hdGhlbWF0aWNhbCBCaW9sb2d5IiwKICAgICAgICAgICAgICAgICAgICAgIkFwcGxpZWQgTWF0aGVtYXRpY3MgKE90aGVyKSIsCiAgICAgICAgICAgICAgICAgICAgICJTdGF0aXN0aWNzIiwKICAgICAgICAgICAgICAgICAgICAgIk1hdGgtc3RhdHMgYW5kIHByb2JhYmlsaXR5IiwKICAgICAgICAgICAgICAgICAgICAgIk1hdGgtc3RhdHMiLAogICAgICAgICAgICAgICAgICAgICAiU3RhdGlzdGljcyAoT3RoZXIpIiwKICAgICAgICAgICAgICAgICAgICAgIk1hdGgtc3RhdHMgKE90aGVyKSIpCnN1YmZpZWxkc19jb2RlcyA8LSBjKDI3LjAxMDEsCiAgICAgICAgICAgICAgICAgICAgIDI3LjAxMDIsCiAgICAgICAgICAgICAgICAgICAgIDI3LjAxMDMsCiAgICAgICAgICAgICAgICAgICAgIDI3LjAxMDUsCiAgICAgICAgICAgICAgICAgICAgIDI3LjAxOSwKICAgICAgICAgICAgICAgICAgICAgMjcuMDMwMSwKICAgICAgICAgICAgICAgICAgICAgMjcuMDMwMywKICAgICAgICAgICAgICAgICAgICAgMjcuMDMwNCwKICAgICAgICAgICAgICAgICAgICAgMjcuMDMwNSwKICAgICAgICAgICAgICAgICAgICAgMjcuMDMwNiwKICAgICAgICAgICAgICAgICAgICAgMjcuMDM5OSwKICAgICAgICAgICAgICAgICAgICAgMjcuMDUwMSwKICAgICAgICAgICAgICAgICAgICAgMjcuMDUwMiwKICAgICAgICAgICAgICAgICAgICAgMjcuMDUwMywKICAgICAgICAgICAgICAgICAgICAgMjcuMDU5OSwKICAgICAgICAgICAgICAgICAgICAgMjcuOTk5OSkKc3ViZmllbGRzX2NvZGVzIDwtIGFzLmNoYXJhY3RlcihzdWJmaWVsZHNfY29kZXMpCnN1YmZpZWxkc19kZiA8LSBkYXRhLmZyYW1lKCJzdWJmaWVsZCI9c3ViZmllbGRzX25hbWVzLCJDSVBDT0RFIj1zdWJmaWVsZHNfY29kZXMpCnN1YmZpZWxkc19kZgpgYGAKCklmIGxpa2UgbWUgeW91IGZpbmQgdGhlIGxpc3Qgb2Ygc3ViZmllbGRzIGtpbmQgb2YgcmlkaWN1bG91cyBhbmQgcmVkdW5kYW50LCBkb24ndCB3b3JyeSwgd2Ugd2lsbCBzaW1wbGlmeSBpdCBsYXRlciBvbiwgYWZ0ZXIgd2UgaGF2ZSBzdW1tYXJpemVkIGEgYml0LiAKCgpXZSBoYXZlIHRoZSBzcGxpdCBkYXRhZnJhbWVzLiBIZXJlIHRoZSBtYXRoIGNvbXBsZXRpb25zIHdpbGwgaGF2ZSBjb2Fyc2VjaXAgPSAyNyBhbmQgdGhlIHN1YmNpcCB3aWxsIGluZGljYXRlIHdoYXQgc3BlY2lhbHR5IHRoZSBkZWdyZWUgd2FzIGluIChsaWtlIHN0YXRzIG9yIG1hdGgtZWNvbikuICAKYGBge3J9CmNvbXBsZXRpb25zX3NwbGl0XzEwICU+JSAKICBzZWxlY3QoVU5JVElELENUT1RBTFQsY29hcnNlY2lwKSAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpCmBgYApUaGVzZSBhcmUgcXVpdGUgbG9uZywgd2l0aCBzZXBhcmF0ZSBjb21wbGV0aW9uIGRhdGEgYnkgZ2VuZGVyIGFuZCBldGhuaWMgbGluZXMuIAoKCmBgYHtyfQpjb21wbGV0aW9uc19zcGxpdF8xMCAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcmlzZSgiMjAxMCI9c3VtKENUT1RBTFQpKQpgYGAKCgpgYGB7cn0KbWF0aF9jb21wbGV0aW9uc18xMCA8LSBjb21wbGV0aW9uc19zcGxpdF8xMCAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxMCc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xMSA8LSBjb21wbGV0aW9uc19zcGxpdF8xMSAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxMSc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xMiA8LSBjb21wbGV0aW9uc19zcGxpdF8xMiAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxMic9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xMyA8LSBjb21wbGV0aW9uc19zcGxpdF8xMyAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxMyc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xNCA8LSBjb21wbGV0aW9uc19zcGxpdF8xNCAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNCc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xNSA8LSBjb21wbGV0aW9uc19zcGxpdF8xNSAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNSc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xNiA8LSBjb21wbGV0aW9uc19zcGxpdF8xNiAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNic9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xNyA8LSBjb21wbGV0aW9uc19zcGxpdF8xNyAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNyc9c3VtKENUT1RBTFQpKQoKbWF0aF9jb21wbGV0aW9uc18xOCA8LSBjb21wbGV0aW9uc19zcGxpdF8xOCAlPiUKICBmaWx0ZXIoY29hcnNlY2lwID09ICcyNycpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxOCc9c3VtKENUT1RBTFQpKQpgYGAKCgpMZXQncyB0YWtlIGEgcGVlcDogCgpgYGB7cn0KbWF0aF9jb21wbGV0aW9uc18xOApgYGAKCgoKTm93IHdlIGhhdmUgYWxsIHRoZSBkYXRhIGZvciBhbGwgdGhlIHN1YmZpZWxkcyBmb3IgdGhlIHllYXJzIDIwMTAtMjAxOCBzdG9yZWQgaW4gbmluZSBzZXBhcmF0ZSB0YWJsZXMuIEFnYWluIHdlIG5lZWQgdG8gbWVyZ2UgdGhlbSAKYGBge3J9Cm1hdGhfY29tcGxldGlvbnMgPC0gbWF0aF9jb21wbGV0aW9uc18xMCAlPiUKICBtZXJnZShtYXRoX2NvbXBsZXRpb25zXzExLGhvdz0nZnVsbCcpICU+JQogIG1lcmdlKG1hdGhfY29tcGxldGlvbnNfMTIsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UobWF0aF9jb21wbGV0aW9uc18xMyxob3c9J2Z1bGwnKSAlPiUKICBtZXJnZShtYXRoX2NvbXBsZXRpb25zXzE0LGhvdz0nZnVsbCcpICU+JQogIG1lcmdlKG1hdGhfY29tcGxldGlvbnNfMTUsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UobWF0aF9jb21wbGV0aW9uc18xNixob3c9J2Z1bGwnKSAlPiUKICBtZXJnZShtYXRoX2NvbXBsZXRpb25zXzE3LGhvdz0nZnVsbCcpICU+JQogIG1lcmdlKG1hdGhfY29tcGxldGlvbnNfMTgsaG93PSdmdWxsJykKaGVhZChtYXRoX2NvbXBsZXRpb25zKQpgYGAKTm93IHdlJ3JlIGNvb2tpbmchIFdlIHVzdCBuZWVkIHRvIHRpZHkgdXAgdGhlIGRhdGEuIAoKTGV0J3MgYWRkIGluIGEgY29sdW1uIG9mIGFsbCB0aGUgc3ViZmllbGQgbmFtZXMsIHRoZW4gdGlkeSBhbG9uZyB0aGUgeWVhciBheGlzLiAKCmBgYHtyfQptYXRoX2NvbXBsZXRpb25zIDwtIG1hdGhfY29tcGxldGlvbnMgJT4lCiAgbWVyZ2Uoc3ViZmllbGRzX2RmKQptYXRoX2NvbXBsZXRpb25zIDwtIG1hdGhfY29tcGxldGlvbnMgJT4lCiAgZ2F0aGVyKCIyMDEwIiwiMjAxMSIsIjIwMTIiLCIyMDEzIiwiMjAxNCIsIjIwMTUiLCIyMDE2IiwiMjAxNyIsIjIwMTgiLGtleT0ieWVhciIsdmFsdWU9ImNvbXBsZXRpb25zIikKbWF0aF9jb21wbGV0aW9ucwpgYGAKCgoKQmVmb3JlIHdlIHZpc3VhbGl6ZSwgaXQgd2lsbCBiZSBoYW5keSB0byBoYXZlIGFiYnJldmlhdGVkIG5hbWVzIGZvciBlYWNoIHN1YmZpZWxkLiAKCmBgYHtyfQpzdWJmaWVsZHNfZGYKYGBgCgoKYGBge3J9CmFiYnJfbWF0aF9zdWJmaWVsZCA8LSBjKCdtYXRoJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2FsZ2VicmEnLAogICAgICAgICAgICAgICAgICAgICAgICAnYW5hbHlzaXMnLAogICAgICAgICAgICAgICAgICAgICAgICAndG9wb2xvZ3knLAogICAgICAgICAgICAgICAgICAgICAgICAnbWF0aG90aGVyJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2FwcGxpZWQnLAogICAgICAgICAgICAgICAgICAgICAgICAnY29tcG1hdGgnLAogICAgICAgICAgICAgICAgICAgICAgICAnY29tcG1hdGgrYXBwbCcsCiAgICAgICAgICAgICAgICAgICAgICAgICdmaW5tYXRoJywKICAgICAgICAgICAgICAgICAgICAgICAgJ21hdGhiaW8nLAogICAgICAgICAgICAgICAgICAgICAgICAnYXBwbG1hdGhvdGgnLAogICAgICAgICAgICAgICAgICAgICAgICAnc3RhdCcsCiAgICAgICAgICAgICAgICAgICAgICAgICdtYXRoc3RhdHByb2InLAogICAgICAgICAgICAgICAgICAgICAgICAnbWF0aHN0YXQnLAogICAgICAgICAgICAgICAgICAgICAgICAnc3RhdG90aGVyJywKICAgICAgICAgICAgICAgICAgICAgICAgJ21hdGhzdGF0b3RoZXInKQpgYGAKCkxldCdzIHVwZGF0ZSB0aGUgc3ViZmllbGRzIGxpc3Qgd2l0aCB0aGVzZSBhYmJyZXZpYXRlZCBuYW1lczoKCmBgYHtyfQpzdWJmaWVsZHNfZGYkYWJicmV2IDwtIGFiYnJfbWF0aF9zdWJmaWVsZApgYGAKCgpJbiBwcmVwYXJpbmcgdGhpcyBsaXN0LCBpdCBqdW1wcyBvdXQgYXQgeW91IGhvdyB0aGVyZSBhcmUgYWN0dWFsbHkgZmV3ZXIgcmVhbCBzdWJmaWVsZHM6IG1hdGgsIGFhcGxpZWQvY29tcHV0YXRpb25hbCBtYXRoLCBzdGF0cy4gCgpMZXRzIHdyaXRlIGEgY3J1ZGUtc3ViZmllbGQgZnVuY3Rpb25zCgpgYGB7cn0KY3J1ZGVfc3ViZmllbGQgPC0gZnVuY3Rpb24odCkgewogIGlmICh0ICVpbiUKICAgICAgYygnYXBwbGllZCcsCiAgICAgICAgJ2NvbXBtYXRoJywKICAgICAgICAnY29tcG1hdGgrYXBwbCcsCiAgICAgICAgJ2Zpbm1hdGgnLAogICAgICAgICdtYXRoYmlvJywKICAgICAgICAnYXBwbG1hdGhvdGgnKSl7CiAgICAnYXBwbGllZCcKICB9CiAgZWxzZSBpZiAodCAlaW4lCiAgICAgIGMoJ21hdGgnLAogICAgICAgICdhbGdlYnJhJywKICAgICAgICAnYW5hbHlzaXMnLAogICAgICAgICd0b3BvbG9neScsCiAgICAgICAgJ21hdGhvdGhlcicpKXsKICAgICdtYXRoJwogIH0KICBlbHNlIGlmICh0ICVpbiUgCiAgICAgIGMoJ3N0YXQnLAogICAgICAgICdtYXRoc3RhdHByb2InLAogICAgICAgICdtYXRoc3RhdCcsCiAgICAgICAgJ3N0YXRvdGhlcicsCiAgICAgICAgJ21hdGhzdGF0b3RoZXInKSl7CiAgICAnc3RhdHMnCiAgfQogIH0KYGBgCkxldCdzIHRlc3Qgb3VyIGZ1bmN0aW9uCgpgYGB7cn0KY3J1ZGVfc3ViZmllbGQoJ2FuYWx5c2lzJykKY3J1ZGVfc3ViZmllbGQoJ3N0YXRvdGhlcicpCmNydWRlX3N1YmZpZWxkKCdtYXRoYmlvJykKYGBgCgpOb3cgbGV0cyBhZGQgYSBjcnVkZSBzdWJmaWVsZCBjb2x1bW4gdG8gdGhlIHN1YmZpZWxkcyBkYXRhZnJhbWUKCmBgYHtyfQoKYGBgCmBgYHtyfQpzdWJmaWVsZHNfZGZfZGVjIDwtIHN1YmZpZWxkc19kZiAlPiUgCiAgbXV0YXRlKGNydWRlID0gbWFwX2NocihhYmJyZXYsY3J1ZGVfc3ViZmllbGQpKQoKYGBgCgpgYGB7cn0KbWF0aF9jb21wbGV0aW9uc19kZWMgPC0gbWF0aF9jb21wbGV0aW9ucyAlPiUKICBtZXJnZShzdWJmaWVsZHNfZGZfZGVjLCBvbiA9ICdDSVBDT0RFJykKc3VtbWFyaXNlZF9tYXRoX2NvbXBsZXRpb25zIDwtIG1hdGhfY29tcGxldGlvbnNfZGVjICU+JQogIGdyb3VwX2J5KGNydWRlLHllYXIpICU+JQogIHN1bW1hcmlzZSgndG90YWxfY29tcGxldGlvbnMnPXN1bShjb21wbGV0aW9ucykpCmBgYAoKCgpMZXQncyB2aXN1YWxpemUuIAoKCgpgYGB7cn0KbWF0aF9zdWJmaWVsZF92aXogPC0gIGdncGxvdChkYXRhID0gc3VtbWFyaXNlZF9tYXRoX2NvbXBsZXRpb25zLCBhZXMoc3RydG9pKHllYXIpLTIwMDAsIHRvdGFsX2NvbXBsZXRpb25zKSkgKwogIGdlb21fcG9pbnQoY29sb3I9InN0ZWVsYmx1ZSIpICsgCiAgbGFicyh0aXRsZSA9ICJNYXRoZW1hdGljcyBkZWdyZWVzIGJ5IHN1YmZpZWxkICgyMDEwLTIwMTgpIiwKICAgICAgIHkgPSAiQ291bnQgb2YgZGVncmVlIGNvbXBsZXRpb25zIiwgeCA9ICJ5ZWFyIikgKyAKICB0aGVtZV9idygpKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpKwogIGZhY2V0X3dyYXAofmNydWRlKQpnZ3NhdmUoJ21hdGhfc3ViZmllbGRfdml6LnBuZycsZHBpPTQwMCkKbWF0aF9zdWJmaWVsZF92aXoKYGBgCldpdGggdGhlIGRpc2NsYWltZXIgdGhhdCB0aGVzZSBhcmUgcHJldHR5IGNydWRlIGJpbnMsIHlvdSBjYW4gc2VlIHN0cm9uZyBncm93dGggYWNyb3NzIHRoZSBib2FyZCwgd2l0aCB0aGUgYXBwbGllZCBmaWVsZHMgKHdoaWNoIGluY2x1ZGVzIGNvbXB1dGF0aW9uYWwgbWF0aCwgYXBwbGllZCBtYXRoLCBhbmQgbWF0aCBiaW8pIGdyb3dpbmcgdGhlIGZhc3Rlc3QgaW4gcmVjZW50IHllYXJzLiAKCkxldCdzIGxvb2sgYXQgcGVyY2VudCBjaGFuZ2VzOiAKCmBgYHtyfQptYXRoX2NvbXBsZXRpb25zX2RlYwpgYGAKCgpgYGB7cn0KbmV0X21hdGhfY29tcGxldGlvbnMgPC0gIG1hdGhfY29tcGxldGlvbnNfZGVjICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDEwIHwgeWVhciA9PSAyMDE4KSAlPiUKICBzZWxlY3QoeWVhcixjb21wbGV0aW9ucyxhYmJyZXYsY3J1ZGUpCm5ldF9tYXRoX2NvbXBsZXRpb25zIDwtIG5ldF9tYXRoX2NvbXBsZXRpb25zICU+JQogIHNwcmVhZCh5ZWFyLGNvbXBsZXRpb25zKQpjb2xuYW1lcyhuZXRfbWF0aF9jb21wbGV0aW9ucyk9Yygnc3ViZmllbGQnLCdjcnVkZV9zdWJmaWVsZCcsJ21hdGhfY29tcHNfMTAnLCdtYXRoX2NvbXBzXzE4JykKbmV0X21hdGhfY29tcGxldGlvbnMgPC0gbmV0X21hdGhfY29tcGxldGlvbnMgJT4lCiAgbXV0YXRlKHBlcmNlbnRfY2hhbmdlID0gMTAwKihtYXRoX2NvbXBzXzE4LyhtYXRoX2NvbXBzXzEwKzEpLTEpLCBuZXRfY2hhbmdlID0gbWF0aF9jb21wc18xOC1tYXRoX2NvbXBzXzEwKSAlPiUKICBhcnJhbmdlKGRlc2MocGVyY2VudF9jaGFuZ2UpKQpuZXRfbWF0aF9jb21wbGV0aW9ucyRwZXJjZW50X2NoYW5nZQp2IDwtIG5ldF9tYXRoX2NvbXBsZXRpb25zJHBlcmNlbnRfY2hhbmdlCnZbMV0gPC0gMTAwCm5ldF9tYXRoX2NvbXBsZXRpb25zJHBlcmNlbnRfY2hhbmdlIDwtIHYKbmV0X21hdGhfY29tcGxldGlvbnMKYGBgCldlIGNhbiBhbHNvIGRvIHRoZSBjcnVkZSB2ZXJzaW9tbiBvZiB0aGlzLiAKCmBgYHtyfQpuZXRfbWF0aF9jcnVkZV9jb21wbGV0aW9ucyA8LSBuZXRfbWF0aF9jb21wbGV0aW9ucyAlPiUKICBncm91cF9ieShjcnVkZV9zdWJmaWVsZCkgJT4lCiAgc3VtbWFyaXNlKHRvdGFsX2NvbXBsZXRpb25zXzEwID0gc3VtKG1hdGhfY29tcHNfMTApLAogICAgICAgICAgICB0b3RhbF9jb21wbGV0aW9uc18xOCA9IHN1bShtYXRoX2NvbXBzXzE4KSkKCm5ldF9tYXRoX2NydWRlX2NvbXBsZXRpb25zIDwtIG5ldF9tYXRoX2NydWRlX2NvbXBsZXRpb25zICU+JQogIG11dGF0ZShuZXRfY2hhbmdlID0gdG90YWxfY29tcGxldGlvbnNfMTgtdG90YWxfY29tcGxldGlvbnNfMTAsCiAgICAgICAgIHBjdF9jaGFuZ2UgPSAodG90YWxfY29tcGxldGlvbnNfMTgtdG90YWxfY29tcGxldGlvbnNfMTApL3RvdGFsX2NvbXBsZXRpb25zXzEwKQpgYGAKCgpMZXQncyB2aXN1YWxpemUgdGhlc2UgZ3Jvd3RoIHJhdGVzOiAKYGBge3J9CnBjdF9tYXRoX3NjYXR0ZXIgPC0gZ2dwbG90KGRhdGE9bmV0X21hdGhfY29tcGxldGlvbnMsYWVzKHg9c3ViZmllbGQseT1wZXJjZW50X2NoYW5nZSkpKwogIGdlb21fcG9pbnQoKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGNvbG9yPSdyZWQnKSsKICBsYWJzKHRpdGxlPSJNYXRoIHN1YmZpZWxkIGdyb3d0aCAoYnkgcGN0KTogMjAxMC0yMDE4Iix4PSdhcmVhJyx5PSdwY3QgY2hhbmdlIGluIGRlZ3JlZSBjb21wbGV0aW9ucycpKwogIGdnc2F2ZSgnbWF0aF9mYXN0ZXN0X3Zpei5wbmcnLGRwaT04MDApKwogIGNvb3JkX2ZsaXAoKQpwY3RfbWF0aF9zY2F0dGVyCgpwY3RfbWF0aF9zY2F0dGVyIDwtIGdncGxvdChkYXRhPW5ldF9tYXRoX2NvbXBsZXRpb25zLGFlcyh4PXN1YmZpZWxkLHk9bmV0X2NoYW5nZSkpKwogIGdlb21fcG9pbnQoKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGNvbG9yPSdyZWQnKSsKICBsYWJzKHRpdGxlPSJNYXRoIHN1YmZpZWxkIGdyb3d0aCAobmV0KTogMjAxMC0yMDE4Iix4PSdhcmVhJyx5PSduZXQgY2hhbmdlIGluIGRlZ3JlZSBjb21wbGV0aW9ucycpKwogIGdnc2F2ZSgnbWF0aF9mYXN0ZXN0X3Zpei5wbmcnLGRwaT04MDApKwogIGNvb3JkX2ZsaXAoKQpwY3RfbWF0aF9zY2F0dGVyCmBgYAoKCgpMZXQncyBsb29rIGF0IGhvdyB0aGUgZGlzdHJpYnV0aW9ucyBoYXZlIHNoaWZ0ZWQgb3ZlciB0aGUgcGVyaW9kLiAoSSBrbm93IHRoYXQgcGllIGNoYXJ0cyBoYXZlIGJlZW4gZGVwcmVjYXRlZCApCgpgYGB7cn0KbmV0X21hdGhfY29tcGxldGlvbnMKYGBgCgpMZXQncyBkbyBzb21lIHRpZHlpbmcKCmBgYHtyfQpuZXRfbWF0aF9jb21wbGV0aW9ucyA8LSBuZXRfbWF0aF9jb21wbGV0aW9ucyAlPiUKICByZW5hbWUoJzIwMTAnPSdtYXRoX2NvbXBzXzEwJywnMjAxOCc9J21hdGhfY29tcHNfMTgnKQpuZXRfbWF0aF9jb21wbGV0aW9ucwpgYGAKCgpgYGB7cn0KdGlkeV9tYXRoX2NvbXBsZXRpb25zIDwtIG5ldF9tYXRoX2NvbXBsZXRpb25zICU+JQogIGdhdGhlcignMjAxMCcsJzIwMTgnLGtleT0neWVhcicsdmFsdWU9J2NvbXBsZXRpb25zJykKdGlkeV9tYXRoX2NvbXBsZXRpb25zCmBgYAoKCmBgYHtyfQpiYXJfMjAxMCA8LSBnZ3Bsb3QodGlkeV9tYXRoX2NvbXBsZXRpb25zLGFlcyh4PXllYXIseT1jb21wbGV0aW9ucyxmaWxsPXN1YmZpZWxkKSkrCiAgZ2VvbV9iYXIocG9zaXRpb249ImRvZGdlIiwKICAgICAgICAgICBzdGF0PSJpZGVudGl0eSIpKwogIHRoZW1lX2J3KCkKYmFyXzIwMTAKYGBgCgpMZXRzIGNydWRpZnkgaXQgYW5kIHN3aXRjaCB0byBwcm9wb3J0aW9ucy4KCmBgYHtyfQpjcnVkZV9iYXJfbWF0aCA8LSBnZ3Bsb3QodGlkeV9tYXRoX2NvbXBsZXRpb25zLGFlcyh4PXllYXIseT1jb21wbGV0aW9ucyxmaWxsPWNydWRlX3N1YmZpZWxkKSkrCiAgZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiLAogICAgICAgICAgIHN0YXQ9ImlkZW50aXR5IiwKICAgICAgICAgICBub3JtYWxpemUgPSBUUlVFKSsKICB0aGVtZV9idygpKwogIHlsYWIoJ1Byb3BvcnRpb24gb2YgYWxsIG1hdGggZGVncmVlIGNvbXBsZXRpb25zJykKZ2dzYXZlKCJjcnVkZV9iYXJfbWF0aC5wbmciLGNydWRlX2Jhcl9tYXRoLGRwaT04MDApCmNydWRlX2Jhcl9tYXRoCgpgYGAKClRoaXMgc2hvd3MgdGhhdCBzdGF0cyBhbmQgYXBwbGllZCBmaWVsZHMgYXJlIHJlYWxseSBncm93aW5nIHdpdGhpbiB0aGUgbWF0aCBtYWpvciwgYWx0aG91Z2ggd2UgaGF2ZSBhbHJlYWR5IHNlZW4gdGhhdCB0aGVyZSBpcyBhIGNvbnNpZGVyYWJsZSBhbW91bnQgb2YgZ3Jvd3RoIGluIGdlbmVyYWwgbWF0aGVtYXRpY3MgZGVncmVlcy4gQW55IGNvbXBhbnkgdGhhdCBpcyB0cnlpbmcgdG8gc2VsbCBib29rcyBhbmQgc29mdHdhcmUgdG8gdGhlc2Ugc3R1ZGVudHMgc2hvdWxkIGxvb2sgaW50byBwdWJsaXNoaW5nIG1vcmUgYXBwbGllZCBhbmQgc3RhdGlzdGljcyB0aXRsZXMuIAoKCgo9PT09PT09PSBVTkZJTklTSEVEIEFOQUxZU0lTIE9GIE9GIENPTVBMRVRJT05TIERBVEEgRk9SIENPTVBVVEVSIFNDSUVOQ0UgPT09PQoKCkJlbG93IHdlIHNrZXRjaCBob3cgdG8gYmVnaW4gY2Fycnlpbmcgb3V0IHRoZSBzYW1lIGFuYWx5c2lzIGZvciB0aGUgQ29tcHV0ZXIgU2NpZW5jZSBjYXRlZ29yeSAoQ29hcnNlQ0lQOiAxMSkuIFRoZSBpbnRlcmVzdGVkIHJlYWRlciBjYW4gdXBkYXRlIHNvbWUgb2YgdGhlIGNvbW1hbmRzIGFuZCBmaW5pc2ggaXQuIAoKYGBge3J9CnN1YmZpZWxkc19uYW1lcyA8LSBjKCJHZW4gQ1MiLCJBSSIsIklUIiwiSW5mb3JtYXRpY3MiLCJDUyAoT3RoZXIpIiwKICAgICAgICAgICAgICAgICAgICAgIkdlbiBwcm9ncmFtIiwiU3BlYy4gcHJvZ3JhbSIsCiAgICAgICAgICAgICAgICAgICAgICJDZXJ0IHByb2dyYW0iLCAiT3RoZXIgcHJvZ3JhbSIsCiAgICAgICAgICAgICAgICAgICAgICJEYXRhIHByb2Nlc3NpbmciLCJJbmZvIHNjaSIsCiAgICAgICAgICAgICAgICAgICAgICJDb21wIHN5cyBhbmFseXN0IiwiRGF0YSBlbnRyeSIsCiAgICAgICAgICAgICAgICAgICAgICJXb3JkIHByb2MiLCJPdGhlciBkYXRhIGVudHJ5IiwKICAgICAgICAgICAgICAgICAgICAgIkNTIiwiV2ViIHBhZ2UgZGVzaWduIiwiRGF0YSBtb2RlbCIsCiAgICAgICAgICAgICAgICAgICAgICJHcmFwaGljcyIsIk1vZGVsbGluZyIsIlNvZnR3YXJlIiwKICAgICAgICAgICAgICAgICAgICAgIk5ldHdvcmtzL1RlbGVjb20iLCJOZXR3b3JrIGFkbWluIiwKICAgICAgICAgICAgICAgICAgICAgIkxBTiBhZG1pbiIsIlNlY3VyaXR5IiwiV2VibWFzdGVyIiwKICAgICAgICAgICAgICAgICAgICAgIklUIFByb2plY3QiLCJDb21wIHN1cHAiLCJJVCBBZG1pbiIsCiAgICAgICAgICAgICAgICAgICAgICJPdGhlciBDUyBTdXBwIikKc3ViZmllbGRzX2NvZGVzIDwtIGMoMTEuMDEwMSwgMTEuMDEwMiwgMTEuMDEwMywgMTEuMDEwNCwKICAgICAgICAgICAgICAgICAgICAgMTEuMDE5OSwgMTEuMDIwMSwgMTEuMDIwMiwgMTEuMDIwMywKICAgICAgICAgICAgICAgICAgICAgMTEuMDI5OSwgMTEuMDMwMSwgMTEuMDQwMSwgMTEuMDUwMSwKICAgICAgICAgICAgICAgICAgICAgMTEuMDYwMSwgMTEuMDYwMiwgMTEuMDY5OSwgMTEuMDcwMSwKICAgICAgICAgICAgICAgICAgICAgMTEuMDgwMSwgMTEuMDgwMiwgMTEuMDgwMywgMTEuMDgwNCwKICAgICAgICAgICAgICAgICAgICAgMTEuMDg5OSwgMTEuMDkwMSwgMTEuMTAwMSwgMTEuMTAwMiwKICAgICAgICAgICAgICAgICAgICAgMTEuMTAwMywgMTEuMTAwNCzCoDExLjEwMDUsIDExLjEwMDYsCiAgICAgICAgICAgICAgICAgICAgIDExLjEwOTksIDExLjk5OTkpCnN1YmZpZWxkc19jb2RlcyA8LSBhcy5jaGFyYWN0ZXIoc3ViZmllbGRzX2NvZGVzKQpzdWJmaWVsZHNfZGYgPC0gZGF0YS5mcmFtZSgic3ViZmllbGQiPXN1YmZpZWxkc19uYW1lcywiQ0lQQ09ERSI9c3ViZmllbGRzX2NvZGVzKQpzdWJmaWVsZHNfZGYKCmNzX2NvbXBsZXRpb25zXzEwIDwtIGRmX2NvbXBsZXRpb25zXzEwICU+JQogIG11dGF0ZShzcGx0Y2lwID0gc3Vic3RyKENJUENPREUsMSwyKSkgJT4lCiAgZmlsdGVyKHNwbHRjaXAgPT0gJzExJykgJT4lCiAgc2VsZWN0KENJUENPREUsIENUT1RBTFQpJT4lCiAgZ3JvdXBfYnkoQ0lQQ09ERSkgJT4lCiAgc3VtbWFyaXplKCcyMDEwJz1zdW0oQ1RPVEFMVCkpCgpjc19jb21wbGV0aW9uc18xMSA8LSBkZl9jb21wbGV0aW9uc18xMSAlPiUKICBtdXRhdGUoc3BsdGNpcCA9IHN1YnN0cihDSVBDT0RFLDEsMikpICU+JQogIGZpbHRlcihzcGx0Y2lwID09ICcxMScpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxMSc9c3VtKENUT1RBTFQpKQoKY3NfY29tcGxldGlvbnNfMTIgPC0gZGZfY29tcGxldGlvbnNfMTIgJT4lCiAgbXV0YXRlKHNwbHRjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKSAlPiUKICBmaWx0ZXIoc3BsdGNpcCA9PSAnMTEnKSAlPiUKICBzZWxlY3QoQ0lQQ09ERSwgQ1RPVEFMVCklPiUKICBncm91cF9ieShDSVBDT0RFKSAlPiUKICBzdW1tYXJpemUoJzIwMTInPXN1bShDVE9UQUxUKSkKCmNzX2NvbXBsZXRpb25zXzEzIDwtIGRmX2NvbXBsZXRpb25zXzEzICU+JQogIG11dGF0ZShzcGx0Y2lwID0gc3Vic3RyKENJUENPREUsMSwyKSkgJT4lCiAgZmlsdGVyKHNwbHRjaXAgPT0gJzExJykgJT4lCiAgc2VsZWN0KENJUENPREUsIENUT1RBTFQpJT4lCiAgZ3JvdXBfYnkoQ0lQQ09ERSkgJT4lCiAgc3VtbWFyaXplKCcyMDEzJz1zdW0oQ1RPVEFMVCkpCgpjc19jb21wbGV0aW9uc18xNCA8LSBkZl9jb21wbGV0aW9uc18xNCAlPiUKICBtdXRhdGUoc3BsdGNpcCA9IHN1YnN0cihDSVBDT0RFLDEsMikpICU+JQogIGZpbHRlcihzcGx0Y2lwID09ICcxMScpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNCc9c3VtKENUT1RBTFQpKQoKY3NfY29tcGxldGlvbnNfMTUgPC0gZGZfY29tcGxldGlvbnNfMTUgJT4lCiAgbXV0YXRlKHNwbHRjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKSAlPiUKICBmaWx0ZXIoc3BsdGNpcCA9PSAnMTEnKSAlPiUKICBzZWxlY3QoQ0lQQ09ERSwgQ1RPVEFMVCklPiUKICBncm91cF9ieShDSVBDT0RFKSAlPiUKICBzdW1tYXJpemUoJzIwMTUnPXN1bShDVE9UQUxUKSkKCmNzX2NvbXBsZXRpb25zXzE2IDwtIGRmX2NvbXBsZXRpb25zXzE2ICU+JQogIG11dGF0ZShzcGx0Y2lwID0gc3Vic3RyKENJUENPREUsMSwyKSkgJT4lCiAgZmlsdGVyKHNwbHRjaXAgPT0gJzExJykgJT4lCiAgc2VsZWN0KENJUENPREUsIENUT1RBTFQpJT4lCiAgZ3JvdXBfYnkoQ0lQQ09ERSkgJT4lCiAgc3VtbWFyaXplKCcyMDE2Jz1zdW0oQ1RPVEFMVCkpCgpjc19jb21wbGV0aW9uc18xNyA8LSBkZl9jb21wbGV0aW9uc18xNyAlPiUKICBtdXRhdGUoc3BsdGNpcCA9IHN1YnN0cihDSVBDT0RFLDEsMikpICU+JQogIGZpbHRlcihzcGx0Y2lwID09ICcxMScpICU+JQogIHNlbGVjdChDSVBDT0RFLCBDVE9UQUxUKSU+JQogIGdyb3VwX2J5KENJUENPREUpICU+JQogIHN1bW1hcml6ZSgnMjAxNyc9c3VtKENUT1RBTFQpKQoKY3NfY29tcGxldGlvbnNfMTggPC0gZGZfY29tcGxldGlvbnNfMTggJT4lCiAgbXV0YXRlKHNwbHRjaXAgPSBzdWJzdHIoQ0lQQ09ERSwxLDIpKSAlPiUKICBmaWx0ZXIoc3BsdGNpcCA9PSAnMTEnKSAlPiUKICBzZWxlY3QoQ0lQQ09ERSwgQ1RPVEFMVCklPiUKICBncm91cF9ieShDSVBDT0RFKSAlPiUKICBzdW1tYXJpemUoJzIwMTgnPXN1bShDVE9UQUxUKSkKYGBgCgpgYGB7cn0KY3NfY29tcGxldGlvbnMgPC0gY3NfY29tcGxldGlvbnNfMTAgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTEsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTIsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTMsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTQsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTUsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTYsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTcsaG93PSdmdWxsJykgJT4lCiAgbWVyZ2UoY3NfY29tcGxldGlvbnNfMTgsaG93PSdmdWxsJykKCgpjc19jb21wbGV0aW9ucyA8LSBjc19jb21wbGV0aW9ucyAlPiUKICBtZXJnZShzdWJmaWVsZHNfZGYpICU+JQogIHNlbGVjdCgtQ0lQQ09ERSkKCmNzX2NvbXBsZXRpb25zIDwtIGNzX2NvbXBsZXRpb25zICU+JQogIGdhdGhlcigiMjAxMCIsIjIwMTEiLCIyMDEyIiwiMjAxMyIsIjIwMTQiLCIyMDE1IiwiMjAxNiIsIjIwMTciLCIyMDE4IixrZXk9InllYXIiLHZhbHVlPSJjb21wbGV0aW9ucyIpCmNzX2NvbXBsZXRpb25zCmBgYApGaW5hbGx5IHdlIGNhbiB2aXN1YWxpemU6IAoKCmBgYHtyfQpjc19zdWJmaWVsZF92aXogPC0gIGdncGxvdChkYXRhID0gY3NfY29tcGxldGlvbnMsIGFlcyh5ZWFyLCBjb21wbGV0aW9ucyxncm91cD0xKSkgKwogIGdlb21fbGluZShjb2xvciA9ICJzdGVlbGJsdWUiLCBzaXplID0gMSkgKwogIGdlb21fcG9pbnQoY29sb3I9InN0ZWVsYmx1ZSIpICsgCiAgbGFicyh0aXRsZSA9ICJDUyBkZWdyZWVzIGJ5IHN1YmZpZWxkICgyMDEwLTIwMTgpIiwKICAgICAgIHkgPSAiQ291bnQgb2YgZGVncmVlIGNvbXBsZXRpb25zIiwgeCA9ICJ5ZWFyIikgKyAKICBmYWNldF93cmFwKH5zdWJmaWVsZCkKZ2dzYXZlKCdkaXNjaXBsaW5lX3Zpei5wbmcnLGRwaT00MDApCmNzX3N1YmZpZWxkX3ZpegpgYGAKCmBgYHtyfQpuZXRfY3NfY29tcGxldGlvbnMgPC0gIGNzX2NvbXBsZXRpb25zICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDEwIHwgeWVhciA9PSAyMDE4KSAlPiUKICBzcHJlYWQoeWVhciwgY29tcGxldGlvbnMpCmNvbG5hbWVzKG5ldF9jc19jb21wbGV0aW9ucykgPC0gYygic3ViZmllbGQiLCJjc19jb21wc18yMDEwIiwiY3NfY29tcHNfMjAxOCIpCm5ldF9jc19jb21wbGV0aW9ucyA8LSBuZXRfY3NfY29tcGxldGlvbnMgJT4lCiAgbXV0YXRlKHBlcmNlbnRfY2hhbmdlID0gMTAwKihjc19jb21wc18yMDE4L2NzX2NvbXBzXzIwMTAtMSksIG5ldF9jaGFuZ2UgPSBjc19jb21wc18yMDE4LWNzX2NvbXBzXzIwMTApICU+JQogIGFycmFuZ2UoZGVzYyhwZXJjZW50X2NoYW5nZSkpCm5ldF9jc19jb21wbGV0aW9ucyRwZXJjZW50X2NoYW5nZQp2IDwtIG5ldF9jc19jb21wbGV0aW9ucyRwZXJjZW50X2NoYW5nZQp2WzFdIDwtIDEwMApuZXRfY3NfY29tcGxldGlvbnMkcGVyY2VudF9jaGFuZ2UgPC0gdgpuZXRfY3NfY29tcGxldGlvbnMKYGBgCkxldCdzIHZpc3VhbGl6ZSB0aGVzZSBncm93dGggcmF0ZXM6IApgYGB7cn0KcGN0X2NzX3NjYXR0ZXIgPC0gZ2dwbG90KGRhdGE9bmV0X2NzX2NvbXBsZXRpb25zLGFlcyh4PXN1YmZpZWxkLHk9cGVyY2VudF9jaGFuZ2UpKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXN1YmZpZWxkKSkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGNvbG9yPSdyZWQnKSsKICBsYWJzKHRpdGxlPSJDUyBzdWJmaWVsZCBncm93dGggKGJ5IHBjdCk6IDIwMTAtMjAxOCIseD0nYXJlYScseT0ncGN0IGNoYW5nZSBpbiBkZWdyZWUgY29tcGxldGlvbnMnKSsKICBnZ3NhdmUoJ2NzX292ZXJhbGxfcGN0X3Zpei5wbmcnLGRwaT00MDApKwogIGNvb3JkX2ZsaXAoKQpwY3RfY3Nfc2NhdHRlcgoKcGN0X2NzX3NjYXR0ZXIgPC0gZ2dwbG90KGRhdGE9bmV0X2NzX2NvbXBsZXRpb25zLGFlcyh4PXN1YmZpZWxkLHk9bmV0X2NoYW5nZSkpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCxjb2xvcj0ncmVkJykrCiAgZ2VvbV9sYWJlbChhZXMobGFiZWw9Zmxvb3IobmV0X2NoYW5nZSkpKSsKICBsYWJzKHRpdGxlPSJDUyBzdWJmaWVsZCBncm93dGggKG5ldCk6IDIwMTAtMjAxOCIseD0nYXJlYScseT0nbmV0IGNoYW5nZSBpbiBkZWdyZWUgY29tcGxldGlvbnMnKSsKICBnZ3NhdmUoJ2NzX292ZXJhbGxfbmV0X3Zpei5wbmcnLGRwaT00MDApKwogIGNvb3JkX2ZsaXAoKQpwY3RfY3Nfc2NhdHRlcgpgYGAKCkxldCdzIGZpbHRlciB0aGlzIGRvd24gYSBiaXQKCmBgYHtyfQooYmVzdF9uZXRfY3MgPC0gbmV0X2NzX2NvbXBsZXRpb25zICU+JQogIGFycmFuZ2UoZGVzYyhuZXRfY2hhbmdlKSkgJT4lCiAgc2xpY2UoMToxMCkpCgooYmVzdF9wY3RfY3MgPC0gbmV0X2NzX2NvbXBsZXRpb25zICU+JQogIGFycmFuZ2UoZGVzYyhwZXJjZW50X2NoYW5nZSkpICU+JQogIHNsaWNlKDE6MTApKQpgYGAKCgpgYGB7cn0KYmVzdF9uZXRfY3Nfc2NhdHRlciA8LSBnZ3Bsb3QoZGF0YT1iZXN0X25ldF9jcyxhZXMoeD1zdWJmaWVsZCx5PXBlcmNlbnRfY2hhbmdlKSkrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zdWJmaWVsZCkpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCxjb2xvcj0ncmVkJykrCiAgbGFicyh0aXRsZT0iQmVzdCBDUyBzdWJmaWVsZCBncm93dGggKGJ5IG5ldCk6IDIwMTAtMjAxOCIseD0nYXJlYScseT0ncGN0IGNoYW5nZSBpbiBkZWdyZWUgY29tcGxldGlvbnMnKSsKICBnZ3NhdmUoJ2NzX2Zhc3Rlc3RfbmV0X3Zpei5wbmcnLGRwaT00MDApKwogIGNvb3JkX2ZsaXAoKQpiZXN0X25ldF9jc19zY2F0dGVyCgpiZXN0X3BjdF9jc19zY2F0dGVyIDwtIGdncGxvdChkYXRhPWJlc3RfcGN0X2NzLGFlcyh4PXN1YmZpZWxkLHk9cGVyY2VudF9jaGFuZ2UpKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXN1YmZpZWxkKSkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGNvbG9yPSdyZWQnKSsKICBsYWJzKHRpdGxlPSJCZXN0IENTIHN1YmZpZWxkIGdyb3d0aCAoYnkgcGN0KTogMjAxMC0yMDE4Iix4PSdhcmVhJyx5PSdwY3QgY2hhbmdlIGluIGRlZ3JlZSBjb21wbGV0aW9ucycpKwogIGdnc2F2ZSgnY3NfZmFzdGVzdF9wY3Rfdml6LnBuZycsZHBpPTQwMCkrCiAgY29vcmRfZmxpcCgpCmJlc3RfcGN0X2NzX3NjYXR0ZXIKYGBgCgpJdCdzIHdvcnRoIG5vdGluZyB3aGF0IHRoZSBkaWZmZXJlbnQgY29kZXMgYXJlOiAKCkRhdGEgbW9kZWxpbmcgaW5jbHVkZXMgRGF0YSBXYXJlaG91c2luZyBhbmQgRGF0YWJhc2UgQWRtaW4KCk1vZGVsbGluZyBpbmNsdWRzIFZpcnR1YWwgRW52aXJvbm1lbnRzIGFuZCBTaW11bGF0aW9uCgpNYW55IGluZm9ybWF0aWNzIGRlZ3JlZXMgYXJlIGJ1ZGRpbmcgaW50byBkYXRhIHNjaWVuY2UgZGlzY2lwbGluZXMgKGUuZy4gYXQgdGhlIFVuaXZlcnNpdHkgb2YgV2FzaGluZ3RvbikuIAoK